</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</h2>\t\t\n\t<time datetime=\"2023-01-08T18:13:09+00:00\" class=\"by-line\">08 Jan 2023, 杭州 | 麦克船长 | 总计 40590 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-1.jpeg\" alt=\"image\" /></p>\n\n<p>原文链接:<a href=\"https://zhuanlan.zhihu.com/p/597586623\">https://zhuanlan.zhihu.com/p/597586623</a></p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一潮流之巅nlp-研究范式的转换\" id=\"markdown-toc-一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</a> <ul>\n <li><a href=\"#1范式转换-10从深度学习到两阶段预训练模型\" id=\"markdown-toc-1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</a> <ul>\n <li><a href=\"#11影响一中间任务的消亡\" id=\"markdown-toc-11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</a></li>\n <li><a href=\"#12影响二不同研究方向技术路线的统一\" id=\"markdown-toc-12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</a></li>\n </ul>\n </li>\n <li><a href=\"#2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\" id=\"markdown-toc-2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</a> <ul>\n <li><a href=\"#21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\" id=\"markdown-toc-21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</a></li>\n </ul>\n </li>\n <li><a href=\"#影响一让-llm-适配人的新型交互接口\" id=\"markdown-toc-影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</a></li>\n <li><a href=\"#影响二很多-nlp-子领域不再具备独立研究价值\" id=\"markdown-toc-影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</a></li>\n <li><a href=\"#影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\" id=\"markdown-toc-影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</a></li>\n </ul>\n </li>\n <li><a href=\"#二学习者从无尽数据到海量知识\" id=\"markdown-toc-二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</a> <ul>\n <li><a href=\"#1求知之路llm-学到了什么知识\" id=\"markdown-toc-1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</a></li>\n <li><a href=\"#2记忆之地llm-如何存取知识\" id=\"markdown-toc-2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</a></li>\n <li><a href=\"#3知识涂改液如何修正-llm-里存储的知识\" id=\"markdown-toc-3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</a></li>\n </ul>\n </li>\n <li><a href=\"#三规模效应当-llm-越来越大时会发生什么\" id=\"markdown-toc-三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</a></li>\n <li><a href=\"#四人机接口从-in-context-learning-到-instruct-理解\" id=\"markdown-toc-四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</a> <ul>\n <li><a href=\"#1神秘的-in-context-learning\" id=\"markdown-toc-1神秘的-in-context-learning\">1、神秘的 In Context Learning</a></li>\n <li><a href=\"#2神奇的-instruct-理解\" id=\"markdown-toc-2神奇的-instruct-理解\">2、神奇的 Instruct 理解</a></li>\n <li><a href=\"#3in-context-learning-和-instruct-的联系\" id=\"markdown-toc-3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</a></li>\n </ul>\n </li>\n <li><a href=\"#五智慧之光如何增强-llm-的推理能力\" id=\"markdown-toc-五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</a> <ul>\n <li><a href=\"#1基于-prompt-的方法\" id=\"markdown-toc-1基于-prompt-的方法\">1、基于 Prompt 的方法</a></li>\n <li><a href=\"#2代码预训练增强-llm-推理能力\" id=\"markdown-toc-2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</a></li>\n <li><a href=\"#3关于-llm-推理能力的思考\" id=\"markdown-toc-3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</a></li>\n </ul>\n </li>\n <li><a href=\"#六未来之路llm-研究趋势及值得研究的重点方向\" id=\"markdown-toc-六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</a> <ul>\n <li><a href=\"#探索-llm-模型的规模天花板\" id=\"markdown-toc-探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</a></li>\n <li><a href=\"#增强-llm-的复杂推理能力\" id=\"markdown-toc-增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</a></li>\n <li><a href=\"#llm-纳入-nlp-之外更多其它研究领域\" id=\"markdown-toc-llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</a></li>\n <li><a href=\"#更易用的人和-llm-的交互接口\" id=\"markdown-toc-更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</a></li>\n <li><a href=\"#建设高难度的综合任务评测数据集\" id=\"markdown-toc-建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</a></li>\n <li><a href=\"#高质量数据工程\" id=\"markdown-toc-高质量数据工程\">高质量数据工程</a></li>\n <li><a href=\"#超大-llm-模型-transformer-的稀疏化\" id=\"markdown-toc-超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</a></li>\n </ul>\n </li>\n <li><a href=\"#七取经之路复刻-chatgpt-时要注意些什么\" id=\"markdown-toc-七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</a></li>\n <li><a href=\"#八chatgpt为什么是-openai\" id=\"markdown-toc-八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</a></li>\n</ul>\n\n<p>ChatGPT 出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型(LLM,Large Language Model)效果能好成这样;惊醒是顿悟到我们对LLM的认知及发展理念,距离世界最先进的想法,差得有点远。我属于既惊喜又惊醒的那一批,也是典型的中国人,中国人善于自我反思,于是开始反思,而这篇文章正是反思的结果。</p>\n\n<p>实话实说,国内在 LLM 模型相关技术方面,此刻,距离最先进技术的差距进一步加大了。技术领先或技术差距这事情,我觉得要动态地以发展的眼光来看。<strong>在 Bert 出现之后的一到两年间,其实国内在这块的技术追赶速度还是很快的,也提出了一些很好的改进模型,差距拉开的分水岭应该是在 GPT 3.0 出来之后,也就是 2020 年年中左右</strong>。在当时,其实只有很少的人觉察到:GPT 3.0 它不仅仅是一项具体的技术,其实体现的是 LLM 应该往何处去的一个发展理念。自此之后,差距拉得越来越远,ChatGPT 只是这种发展理念差异的一个自然结果。所以,我个人认为,抛开是否有财力做超大型 LLM 这个因素,如果单从技术角度看,差距主要来自于对 LLM 的认知以及未来应往何处去的发展理念的不同。</p>\n\n<p>国内被国外技术甩得越来越远,这个是事实,不承认也不行。前阵子网上很多人担忧说国内 AI 现在处于「危急存亡之秋」,我觉得倒也不至于这么严重。君不见,这个世界上,具备这么超前眼光的只有 OpenAI 一家吗?<strong>包括 Google 在内,其实对于 LLM 发展理念的理解,明显都落后 OpenAI 一个身位。现实是 OpenAI 表现过于优秀,把所有人都甩开了,不仅仅是国内</strong>。</p>\n\n<p>我觉得,OpenAI 对 LLM 在理念及相关技术方面,领先国外的 Google、DeepMind 大约半年到一年的时间,领先国内大概两年左右的时间。在 LLM 这个事情上,感觉梯队很明显,Google 应该是排在第二位,最能体现 Google 技术眼光的是 PaLM 和 Pathways,推出时间大概在 22 年 2 月到 4 月间,同一时期,OpenAI 推出的却是 InstructGPT,从这里就可以看出 Google 和 OpenAI 的差距了,至于为何这么说,你看了我后面的正文后大概能理解。DeepMind 之前的重心一直在强化学习攻克游戏和 AI for science 这些方面,切入LLM 其实很晚,应该是21 年才开始重视这个方向,目前也处于追赶状态。Meta 就更不用说了,重心一直不在 LLM 上,目前感觉也发力开始追赶。这还是目前做得最好的一批机构,尚且如此,更何况国内呢?我觉得情有可原。至于 OpenAI 关于 LLM 的理念是什么,我在本文的最后一部分,会谈谈我的认知。</p>\n\n<p>本文梳理自 GPT 3.0 出现之后的主流 LLM 技术,能够让您对 LLM 领域的技术脉络,LLM 技术发展过程中出现过的不同发展理念,乃至未来可能的发展趋势,有比较清晰的认知。当然,很多地方讲的内容是我个人看法,有很大的主观性,错漏难免,所以还请谨慎参考。</p>\n\n<p>本文试图回答下面一些问题:ChatGPT 是否带来了 NLP 乃至 AI 领域的研究范式转换?如果是,那会带来怎样的影响?LLM 从海量数据中学到了什么知识?LLM 又是如何存取这些知识的?随着LLM规模逐步增大,会带来什么影响?什么是 In Context Learning?为什么它是一项很神秘的技术?它和 Instruct 又是什么关系?LLM 具备推理能力吗?思维链 CoT 又是怎么做的?等等,相信看完,能让您对这些问题有一个答案。</p>\n\n<p>首先,在谈 LLM 技术现状前,先宏观地谈下我心目中的研究范式转换问题。这样,我们才能「先见森林,再见树木」,对具体技术为何会是如此变化有个更清晰的认知。</p>\n\n<h2 id=\"一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</h2>\n\n<p>如果我们把时间线往前拉得更长一些,回到 NLP 领域的深度学习时代,在更长时间窗口内观察技术变迁及其影响,可能会更容易看清其中的一些关键节点。我个人认为,在最近 10 年来NLP领域的技术发展过程中,可能存在两次大的研究范型转换。</p>\n\n<h3 id=\"1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在深度学习引入 NLP 领域(2013 年左右),到 GPT 3.0 出现之前(2020 年 5 月左右)。</p>\n\n<p>在 Bert 和 GPT 模型出现之前,NLP 领域流行的技术是深度学习模型,而 NLP 领域的深度学习,主要依托于以下几项关键技术:以大量的改进 LSTM 模型及少量的改进 CNN 模型作为典型的特征抽取器;以 Sequence to Sequence(或叫 encoder-decoder 亦可)+ Attention 作为各种具体任务典型的总体技术框架。</p>\n\n<p>在这些核心技术加持下,NLP 领域深度学习的主要研究目标,如果归纳一下,是如何有效增加模型层深或模型参数容量。就是说,怎么才能往 encoder 和 decoder 里不断叠加更深的 LSTM 或 CNN 层,来达成增加层深和模型容量的目标。这种努力,尽管确实不断增加了模型层深,但是从解决具体任务的效果角度看,总体而言,不算很成功,或者说和非深度学习方法相比,带来的优势不算大。</p>\n\n<p>深度学习之所以不够成功,我认为主要原因来自于两个方面:一方面是某个具体任务有限的训练数据总量。随着模型容量的增加,需要靠更大量的训练数据来支撑,否则即使你能把深度做起来,任务效果也做不上去。而在预训练模型出现之前,很明显这是 NLP 研究领域一个严重问题;另外一个方面是 LSTM/CNN 特征抽取器,表达能力不够强。意思是就算给你再多的数据也没用,因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因,阻碍了深度学习在 NLP 领域的成功突围。</p>\n\n<p>Bert / GPT 这两个预训练模型的出现,无论在学术研究角度看,还是工业应用角度来看,都代表了 NLP 领域的一个技术飞跃,并带来了整个领域研究范式的转换。这种范式转换带来的影响,体现在两个方面:首先,是部分 NLP 研究子领域的衰退乃至逐步消亡;其次,NLP 不同子领域的技术方法和技术框架日趋统一,在 Bert 出现后一年左右,技术栈基本收敛到两种技术模式中。关于这两点,我们分头来谈。</p>\n\n<h4 id=\"11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</h4>\n\n<p>NLP 是一个宏观研究领域的统称,里面有五花八门具体的子领域与子方向,如果仔细分析,从任务的性质角度,可以把这些任务分成两大类:一类可以叫做「中间任务」,一类可以称为「最终任务」。</p>\n\n<p>典型的中间任务包括:中文分词、词性标注、NER、句法分析、指代消解、语义 Parser 等,这类任务一般并不解决应用中的实际需求,大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的,比如几乎没有需求说,我要一个句法 Parser,把这个句子的句法分析树给用户看看,用户不需要看到这些NLP的中间阶段处理结果,他只关心某个具体任务你有没有干好。「最终任务」包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等,有很多。这类任务的特点是每个子领域都解决某个实际需求,任务结果基本能直接呈现给用户,比如用户确实存在给你一句英文,告诉他中文是什么的需求。</p>\n\n<p>按理说,「中间任务」就不应该出现,而之所以会存在,这是 NLP 技术发展水平不够高的一种体现。在技术发展早期阶段,因为当时的技术相对落后,很难一步做好有难度的最终任务。比如机器翻译,早期技术要做好机器翻译是很困难的,于是科研人员就把难题分而治之,分解成分词、词性标注、句法分析等各种中间阶段,先把每个中间阶段做好,然后再拼起来完成最终任务,这也是没办法的事情。</p>\n\n<p>但是自从 Bert/GPT 出现之后,其实就没有必要做这些中间任务了,因为通过大量数据的预训练,Bert/GPT 已经把这些中间任务作为语言学特征,吸收到了 Transformer 的参数里,此时我们完全可以端到端地直接解决那些最终任务,而无须对这种中间过程专门建模。这里可能争议最大的是中文分词,其实道理也是一样的,哪些字应该组成一个词,这个其实你不用管,让 LLM 自己当特征去学就行了,只要对于解决任务有帮助,它自然会去学该学的合理分词方式,也未必一定要和我们人类理解的分词规则相同。</p>\n\n<p>基于以上认知,其实在Bert/GPT一出现,你就应该得出这类NLP的中间阶段的任务,会逐步退出历史舞台这个结论。</p>\n\n<h4 id=\"12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</h4>\n\n<p>在说明具体影响前,我们先讨论下另外一种 NLP 任务划分方式,这对于理解后面内容有帮助。如果对「最终任务」进一步进行分类,又大致可以分为两大不同类型的任务:自然语言理解类任务和自然语言生成类任务。如果排除掉「中间任务」的话,典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等,这种任务本质上都是分类任务,就是说输入一个句子(文章),或者两个句子,模型参考所有输入内容,最后给出属于哪个类别的判断。自然语言生成也包含很多 NLP 研究子方向,比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本,对应地,模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上。</p>\n\n<p>自从 Bert/GPT 模型诞生后,出现了明显的技术统一趋向。首先,NLP 中不同的子领域,其特征抽取器都逐渐从 LSTM/CNN 统一到 Transformer 上。其实,自Bert公开后不久,就应该意识到,这必然会成为技术趋势。而且,目前 Transformer 不仅统一了 NLP 诸多领域,也正在逐步地替换图像处理各种任务中被广泛使用的 CNN 等其它模型的进程之中,类似的,多模态模型目前也基本都采用了 Transformer 模型。这种Transformer从NLP出发,攻城略地逐步统一AI越来越多领域的趋势,起始于 2020 年底出现的 Vision Transformer (ViT) ,之后蓬勃发展,到目前已大获成功,且其继续向更多领域拓展的势头会越来越迅猛。</p>\n\n<p>其次,大多数 NLP 子领域的研发模式切换到了两阶段模式:模型预训练阶段 + 应用微调(Fine-tuning)或应用 Zero/Few Shot Prompt 模式。更准确地说,NLP 各种任务其实收敛到了两个不同的预训练模型框架里:对于自然语言理解类任务,其技术体系统一到了以 Bert 为代表的「双向语言模型预训练 + 应用 Fine-tuning」模式;而对于自然语言生成类任务,其技术体系则统一到了以GPT 2.0 为代表的「自回归语言模型(即从左到右单向语言模型)+ Zero/Few Shot Prompt」模式。至于为何会分化成两条技术路线,有其必然性,关于这点我们放在后面解释。</p>\n\n<p>这两种模式,看似比较相像,但其背后蕴含了迥异的发展思路,也会导向不同的未来发展方向。不过遗憾的是,我们中的绝大多数人,在当时都低估了GPT 这条发展路线的潜力,而把视觉中心聚焦到了Bert这种模式上。</p>\n\n<h3 id=\"2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在 GPT 3.0 出现之后(20 年 6 月左右),一直到目前为止,我们应该正处于这个范式转换过程中。</p>\n\n<p>ChatGPT 是触发这次范型转换的关键节点,但是在 InstructGPT 出现之前,其实 LLM 处于这次范式转换前的一个过渡期。</p>\n\n<h4 id=\"21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</h4>\n\n<p>前面说过,在预训练模型发展的早期,技术框架收敛到了 Bert 模式和 GPT 模式这两种不同的技术范型,而且人们普遍更看好 Bert 模式一些,相当多数的后续技术改进,都是沿着 Bert 那条路走的。但是,随着技术的继续发展,你会发现,目前规模最大的 LLM 模型,几乎清一色都是类似 GPT 3.0 这种「自回归语言模型 + Prompting」模式的,比如 GPT-3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA 等,没有例外。为什么会这样呢?背后一定有其必然性,我认为可能主要源于两个原因。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-2.jpeg\" alt=\"image\" /></p>\n\n<p>首先,Google 的 T5 模型,在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示,标为红色的是个文本分类问题,黄色的是判断句子相似性的回归或分类问题,这都是典型的自然语言理解问题。在 T5 模型里,这些自然语言理解问题在输入输出形式上和生成问题保持了一致,也就是说,可以把分类问题转换成让 LLM 模型生成对应类别的字符串,这样理解和生成任务在表现形式就实现了完全的统一。</p>\n\n<p>这说明自然语言生成任务,在表现形式上可以兼容自然语言理解任务,若反过来,则很难做到这一点。这样的好处是:同一个 LLM 生成模型,可以解决几乎所有 NLP 问题。而如果仍然采取 Bert 模式,则这个 LLM 模型无法很好处理生成任务。既然这样,我们当然倾向于使用生成模型,这是一个原因。</p>\n\n<p>第二个原因,如果想要以零示例提示语(zero shot prompting)或少数示例提示语(few shot prompting)的方式做好任务,则必须要采取 GPT 模式。现在已有研究(参考:<a href=\"https://arxiv.org/pdf/2205.11726\">《On the Role of Bidirectionality in Language Model Pre-Training》</a>)证明:如果是以 fine-tuning 方式解决下游任务,Bert模式的效果优于 GPT 模式;若是以 zero shot / few shot prompting 这种模式解决下游任务,则GPT模式效果要优于 Bert 模式。这说明了,生成模型更容易做好 zero shot/few shot prompting 方式的任务,而Bert模式以这种方式做任务,是天然有劣势的。这是第二个原因。</p>\n\n<p>但是问题来了:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?要解释清楚这个问题,我们首先需要搞清楚另外一个问题:什么样的 LLM 模型,对我们是最理想的?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-3.jpeg\" alt=\"image\" /></p>\n\n<p>上图展示了一个理想的 LLM 该有的样子。首先,LLM 应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它,它应该能够自动从中学习到里面包含的所有知识点,学习过程不需要人的介入,并且能灵活应用所学知识,来解决实际问题。因为数据是海量的,要吸收所有知识,就要非常多的模型参数来存储知识,所以这个模型必然会是一个巨无霸模型。</p>\n\n<p>其次,LLM 应该能解决 NLP 任何子领域的问题,而不仅支持有限领域,甚至它应该可以响应 NLP 之外其它领域的问题,最好是任意领域的问题都能得到很好地回答。</p>\n\n<p>再者,当我们使用 LLM 解决某个具体领域问题的时候,应该用我们人类习惯的表达方式,就是说LLM应该理解人类的命令。这体现出让 LLM 适配人,而不是反过来,让人去适配 LLM 模型。人适配 LLM 的典型例子,比如绞尽脑汁去尝试各种不同的 prompt,以试图找到好的提示语,才能很好地解决手头问题。关于这点,上图在人类和 LLM 交互的接口层,举了几个例子,说明什么是好的人使用 LLM 模型的接口形式。</p>\n\n<p>看完这个理想中的 LLM,我们再回头解释上面遗留的问题:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?有两个原因。</p>\n\n<p>第一,这个 LLM 模型规模必然非常巨大,有能力作出这个模型,或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人,就算你把模型开源出来,他们也无力部署这个模型,更不用说再用 Fine-tuning 这种模式去修改模型参数了。所以,我们应该追求不修正模型参数,就能让任务需求方完成任务的方式,也就是应该采取 prompt 模式完成任务,而非 Fine-tuning 模式(由此可看出,soft prompting 技术方向是违背这个发展趋势的)。模型制作方则将 LLM 作成公用服务,以 LLM as Service 的模式运行。作为服务支持方,考虑到千变万化的用户需求,所以 LLM 模型制作方更要追求让 LLM 能完成尽可能多类型的任务,这是附带的影响,也是为何超级大模型一定会追求走向AGI的现实因素。</p>\n\n<p>第二,zero shot prompting 也好,few shot prompting 也好,甚至促进LLM推理能力的思维链(CoT,Chain of Thought)Prompting 也好,就是上图中接口层中的现有技术。具体而言,zero shot prompting 的初衷,其实就是人类和 LLM 的理想接口,直接用人类所习惯的任务表述方式让 LLM 做事情,但是发现 LLM 并不能很好地理解,效果也不好。经过继续研究,转而发现:对于某项任务,如果给 LLM 几个示例,用这些示例来代表任务描述,效果会比 zero shot prompting 好,于是大家都去研究更好的 few shot prompting 技术。可以理解为,本来我们希望 LLM 能够用人类常用的命令方式来执行某个任务,但是目前技术还做不到,所以退而求其次,用这些替代技术来表达人类的任务需求。</p>\n\n<p>如果理解了上述逻辑,很容易得出如下结论:few shot prompting(也被称为In Context Learning)只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务,而且 LLM 可以理解,那么,我们肯定会毫不犹豫地抛弃这些过渡期的技术,原因很明显,用这些方法来描述任务需求,并不符合人类的使用习惯。</p>\n\n<p>这也是为何我将 GPT 3.0 + Prompting 列为过渡期技术的原因,ChatGPT 的出现,改变了这个现状,用 Instruct 取代了 Prompting,由此带来新的技术范式转换,并产生若干后续影响。</p>\n\n<h3 id=\"影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</h3>\n\n<p>在理想 LLM 的背景下,我们再来看 ChatGPT,能更好理解它的技术贡献。ChatGPT 应该是目前所有的现有技术里,最接近理想 LLM 的技术方法。如果归纳下 ChatGPT 最突出特点的话,我会用下面八个字「能力强大,善解人意」。</p>\n\n<p>「能力强大」这一点,我相信应该主要归功于 ChatGPT 所依托的基础 LLM GPT-3.5。因为 ChatGPT 尽管加入了人工标注数据,但是量级只有数万,这个规模的数据量,和训练 GPT 3.5 模型使用的几千亿 token 级别的数据量相比,包含的世界知识(数据中包含的事实与常识)可谓沧海一粟,几可忽略,基本不会对增强 GPT 3.5 的基础能力发挥什么作用。所以它的强大功能,应该主要来自于隐藏在背后的 GPT 3.5。GPT 3.5 对标理想 LLM 模型中的那个巨无霸模型。</p>\n\n<p>那么,ChatGPT 向 GPT 3.5 模型注入新知识了吗?应该是注入了,这些知识就包含在几万人工标注数据里,不过注入的不是世界知识,而是人类偏好知识。所谓「人类偏好」,包含几方面的含义:首先,是人类表达一个任务的习惯说法。比如,人习惯说「把下面句子从中文翻译成英文」,以此表达一个「机器翻译」的需求,但是 LLM 又不是人,它怎么会理解这句话到底是什么意思呢?你得想办法让 LLM 理解这句命令的含义,并正确执行。所以,ChatGPT 通过人工标注数据,向GPT 3.5 注入了这类知识,方便 LLM 理解人的命令,这是它“善解人意”的关键。其次,对于什么是好的回答,什么是不好的回答,人类有自己的标准,例如比较详细的回答是好的,带有歧视内容的回答是不好的,诸如此类。这是人类自身对回答质量好坏的偏好。人通过 Reward Model 反馈给 LLM 的数据里,包含这类信息。总体而言,ChatGPT 把人类偏好知识注入 GPT 3.5,以此来获得一个听得懂人话、也比较礼貌的 LLM。</p>\n\n<p>可以看出,ChatGPT 的最大贡献在于:基本实现了理想 LLM 的接口层,让 LLM 适配人的习惯命令表达方式,而不是反过来让人去适配 LLM,绞尽脑汁地想出一个能 Work 的命令(这就是 instruct 技术出来之前,prompt 技术在做的事情),而这增加了 LLM 的易用性和用户体验。是 InstructGPT / ChatGPT 首先意识到这个问题,并给出了很好的解决方案,这也是它最大的技术贡献。相对之前的 few shot prompting,它是一种更符合人类表达习惯的人和 LLM 进行交互的人机接口技术。</p>\n\n<p>而这必将启发后续的 LLM 模型,继续在易用人机接口方面做进一步的工作,让 LLM 更听话。</p>\n\n<h3 id=\"影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</h3>\n\n<p>就 NLP 领域而言,这次范式转换,意味着很多目前独立存在的 NLP 研究领域,将被纳入 LLM 的技术体系,进而不再独立存在,逐步消失。经过第一次范式转换,尽管 NLP 中很多「中间任务」,继续作为独立研究领域存在不再必要,但是大多数「最终任务」,仍然是以独立研究领域存在的,只是切换成在「预训练 + fine-tuning」框架下,面对领域独有问题,陆续提出新的改进方案。</p>\n\n<p>目前研究表明,很多 NLP 任务,随着 LLM 模型规模增长,效果会大幅提升。据此,我觉得可得到如下推论:大多数某领域所谓「独有」的问题,大概率只是缺乏领域知识导致的一种外在表象,只要领域知识足够多,这个所谓领域独有的问题,就可以被很好地解决掉,其实并不需要专门针对某个具体领域问题,冥思苦想去提出专用解决方案。也许 AGI 的真相超乎意料地简单:你只要把这个领域更多的数据交给 LLM,让它自己学习更多知识即可。</p>\n\n<p>在这个背景下,同时,ChatGPT 证明了我们现在是可以直接去追求理想 LLM 模型的,那么,未来的技术发展趋势应该是:追求规模越来越大的 LLM 模型,通过增加预训练数据的多样性,来涵盖越来越多的领域,LLM 自主从领域数据中通过预训练过程学习领域知识,随着模型规模不断增大,很多问题随之得到解决。研究重心会投入到如何构建这个理想 LLM 模型,而非去解决某个领域的具体问题。这样,越来越多 NLP 的子领域会被纳入 LLM 的技术体系,进而逐步消失。</p>\n\n<p>我认为,判断某个具体领域是否该立即停止独立研究,其判断标准可采取以下两种方法,占其一即可:第一,判断某个任务,是否 LLM 的研究效果超过人类表现,对于那些 LLM 效果超过人类的研究领域,已无独立研究的必要。举个例子,GLUE 与 SuperGLUE 测试集合里的很多任务,目前 LLM 效果已超过人类表现,与这个数据集合密切相关的研究领域,其实就没有继续独立存在的必要。第二,对比两种模式的任务效果,第一种模式是用较大的领域专用数据进行 Fine-tuning,第二种是 few-shot prompting 或 instruct-based 方法。如果第二种方法效果达到或超过第一种方法,则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看,其实很多研究领域,目前 fine-tuning 效果还是占优的(因为这种模式领域训练数据量大),看似还可独立存在。但是考虑到很多任务随着模型规模增大,few shot prompting 效果持续增长,随着更大模型的出现,这个拐点很可能短期就会达到。</p>\n\n<p>如果上述猜测成立,将意味着如下残酷事实:对于很多 NLP 领域的研究人员,将面临往何处去的选择,是继续做领域独有问题呢?还是放弃这种看似前途不大的方式,转而去建设更好的LLM?如果选择转向去建设 LLM,又有哪些机构有能力、有条件去做这个事情呢?你对这个问题的回答会是什么呢?</p>\n\n<h3 id=\"影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</h3>\n\n<p>如果站在 AGI 的视角,参照之前描述的理想 LLM 模型,它所能完成的任务,不应局限于 NLP 领域,或某一两个学科领域,理想中的 LLM 应该是领域无关的通用人工智能模型,它现在在某一两个领域做得好,不代表只能做这些任务。ChatGPT 的出现,证明了现在这个时期,我们去追求AGI是有可行性的,而现在是抛开「领域学科」这个思维束缚的时候了。</p>\n\n<p>ChatGPT 除了展示出以流畅的对话形式解决各种 NLP 任务外,也具备强大的代码能力。很自然的,之后越来越多其它的研究领域,也会被逐步纳入 LLM 体系中,成为通用人工智能的一部分。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-4.jpeg\" alt=\"image\" /></p>\n\n<p>LLM 从 NLP 向外进行领域拓展,一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入,让LLM成为一个支持多模态输入输出的通用人机接口,典型的例子包括 DeepMind 的 Flamingo 和微软的<a href=\"https://arxiv.org/pdf/2206.06336.pdf\">《Language Models are General-Purpose Interfaces》</a>,上图展示了这种方式的概念结构。</p>\n\n<p>我的判断是无论是图像还是多模态,未来被融入 LLM 成为好用的功能,可能比我们想象的进度要慢。主要原因在于:尽管图像领域最近两年也一直在模仿 Bert 预训练的路子,尝试引入自监督学习,释放模型自主从图像数据中学习知识的能力,典型技术就是“对比学习”和 MAE,这是两条不同的技术路线。然而,从目前效果来看,尽管取得了很大的技术进步,但貌似这条路尚未走通,这体现在图像领域预训练模型应用到下游任务,带来的效果收益,远不如 Bert 或 GPT 应用在 NLP 下游任务那样显著。所以,图像预处理模型仍需深入探索,以释放图像数据的潜力,而这会迟滞它们被统一到 LLM 大模型的时间。当然,如果哪天这条路被趟通,大概率会复现NLP领域目前的局面,就是图像处理各个研究子领域可能会逐步消失,被融入到大型 LLM 中来,直接完成终端任务。</p>\n\n<p>除了图像与多模态,很明显,其它领域也会逐渐被纳入到理想 LLM 中来,这个方向方兴未艾,是具备高价值的研究主题。</p>\n\n<p>以上是我对范式转换的个人思考,接下来,我们来梳理下 GPT 3.0 之后 LLM 模型的主流技术进展。如理想 LLM 模型所示,相关的技术其实可以分为两大类;一类是关于 LLM 模型如何从数据中吸收知识,也包括模型规模增长对 LLM 吸收知识能力带来的影响;第二类是关于人如何使用 LLM 内在能力来解决任务的人机接口,包括In Context Learning 和 Instruct 两种模式。思维链(CoT)prompting 这种 LLM 推理技术,本质上也属于 In Context Learning,因为比较重要,我就把它们单独拎出来讲一下。</p>\n\n<h2 id=\"二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</h2>\n\n<p>从目前研究结果看,Transformer 是足够强大的特征抽取器,尚不需要做特别的改进。那么通过预训练过程,Transformer 学到了什么?知识是如何存取的?我们又如何修正错误知识?本节讲述这方面的研究进展。</p>\n\n<h3 id=\"1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</h3>\n\n<p>LLM 从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,可以分为语言类知识和世界知识两大类。</p>\n\n<p>语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于 LLM 能否捕获语言知识有较长研究历史,自从 Bert 出现以来就不断有相关研究,很早就有结论,各种实验充分证明 LLM 可以学习各种层次类型的语言学知识,这也是为何使用预训练模型后,各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外,各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在 Transformer 的低层和中层,而抽象的语言知识比如语义类知识,广泛分布在 Transformer 的中层和高层结构中。</p>\n\n<p>世界知识指的是在这个世界上发生的一些真实事件(事实型知识,Factual Knowledge),以及一些常识性知识(Common Sense Knowledge)。比如「拜登是现任美国总统」、「拜登是美国人」、「乌克兰总统泽连斯基与美国总统拜登举行会晤」,这些都是和拜登相关的事实类知识;而「人有两只眼睛」、「太阳从东方升起」这些属于常识性知识。关于 LLM 模型能否学习世界知识的研究也有很多,结论也比较一致:LLM 确实从训练数据中吸收了大量世界知识,而这类知识主要分布在 Transformer 的中层和高层,尤其聚集在中层。而且,随着 Transformer 模型层深增加,能够学习到的知识数量逐渐以指数级增加(可参考<a href=\"https://arxiv.org/pdf/2106.02902.pdf\">《BERTnesia: Investigating the capture and forgetting of knowledge in BERT》</a>)。其实,你把 LLM 看作是一种以模型参数体现的隐式知识图谱,如果这么理解,我认为是一点问题也没有的。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2011.04946\">《When Do You Need Billions of Words of Pre-training Data?》</a>这篇文章研究了预训练模型学习到的知识量与训练数据量的关系,它的结论是:对于 Bert 类型的语言模型来说,只用 1000 万到 1 亿单词的语料,就能学好句法语义等语言学知识,但是要学习事实类知识,则要更多的训练数据。这个结论其实也是在意料中的,毕竟语言学知识相对有限且静态,而事实类知识则数量巨大,且处于不断变化过程中。而目前研究证明了随着增加训练数据量,预训练模型在各种下游任务中效果越好,这说明了从增量的训练数据中学到的更主要是世界知识。</p>\n\n<h3 id=\"2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</h3>\n\n<p>由上可知,LLM 确实从数据中学到了很多语言类及世界知识。那么,对于某条具体的知识,LLM 把它存储到了哪里?又是如何提取出来的?这也是一个有意思的问题。</p>\n\n<p>显然,知识一定存储在 Transformer 的模型参数里。从 Transformer 的结构看,模型参数由两部分构成:<strong>多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中</strong>。MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点,那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-5.jpeg\" alt=\"image\" /></p>\n\n<p>但这样的定位,粒度还是太粗,无法很好回答具体某条知识是如何存储与提取的,比如「中国的首都是北京」这条知识,以三元组表达就是<北京,is-capital-of,中国>,其中「is-capital-of」代表实体间关系。<strong>这条知识它存储在 LLM 的哪里呢?</strong></p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:这些知识存哪了?我们现在有以下几点认知:<br />\n1、多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中。<br />\n2、MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点。<br />\n3、那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。<br />\n4、一些研究达成共识:Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,比如《Transformer Feed-Forward Layers Are Key-Value Memories》</p>\n</blockquote>\n\n<p><a href=\"https://arxiv.org/pdf/2012.14913.pdf\">《Transformer Feed-Forward Layers Are Key-Value Memories》</a>给出了一个比较新颖的观察视角,它把 Transformer 的 FFN 看成存储大量具体知识的 Key-Value 存储器。如上图所示(图左是原始论文图,其实不太好理解,可以看做了注释的图右,更好理解些),FFN 的第一层是个 MLP 宽隐层,这是 Key 层;第二层是 MLP 窄隐层,是 Value 层。FFN 的输入层其实是某个单词对应的 MHA 的输出结果Embedding,也就是通过 Self Attention,将整个句子有关的输入上下文集成到一起的 Embedding,代表了整个输入句子的整体信息。</p>\n\n<p>Key 层的每个神经元节点,记载了一对信息。比如对于上图中 FFN 第一个隐层的第 i 个节点 ki,也许就是它记载了 <北京,is-capital-of,中国> 这条知识。ki 节点对应的 key 向量,其实指的是节点 ki 和输入层每个节点的权重向量;而对应的 Value 向量,指的是节点 ki 和 FFN 第二层的 Value 层每个节点形成连接的权重向量。每个神经元的 Key 向量,用于识别输入中的某种语言或者知识模式,是一种模式探测器。如果输入中包含它要检测的某种模式,那么输入向量和 ki 节点的 key 权重进行向量内积计算,加上 Relu,形成 ki 的大数值响应,意味着 ki 检测到了这个模式,于是再把这个响应值,通过 ki 节点的 Value 权重向量向 FFN 第二层传播。这等价于将 Value 向量的值,用响应值加权,然后传递并体现到第二层 Value 层每个节点的输出上。如此这般,FFN 的正向传播计算过程,看起来就像是通过 Key 检测到某种知识模式,然后取出对应的 Value,并把 Value 体现在FFN的第二层输出上。当然,FFN 第二层每个节点,会收集 FFN 的 Key 层所有节点信息,所以是一种混合响应,而 Value 层所有节点的混合响应,可以解读为代表输出单词的概率分布信息。</p>\n\n<p>听着可能还是比较复杂,我们用个极端的例子来说明。我们假设上图的节点 ki就是记载 <北京,is-capital-of,中国>这条知识的 Key-Value 存储器,它的 Key 向量,用于检测「中国的首都是…」这个知识模式,它的 Value 向量,基本存储了与单词「北京」的 Embedding 比较接近的向量。当 Transformer 的输入是「中国的首都是[Mask]」的时候,ki 节点从输入层探测到这个知识模式,所以产生较大的响应输出。我们假设 Key 层其它神经元对这个输入都没有任何响应,那么对应的Value层的节点,其实只会接收到「北京」这个 Value 对应的单词 embedding,并通过 ki的大响应值,进行了进一步的数值放大。于是,Mask 位置对应的输出,就自然会输出「北京」这个单词。基本就是这么个过程,看着很复杂,其实很简单。</p>\n\n<p>而且这篇文章还指出,Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,就是说低层FFN存储词法、句法等表层知识,中层和高层存储语义及事实概念知识,这和其它研究结论是一致的。</p>\n\n<p><strong>要我猜,把 FFN 看成 Key-Value 存储器这种思路,很可能不是最终的正确答案,但是距离最终正确答案的距离,估计也不太远</strong>。</p>\n\n<h3 id=\"3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</h3>\n\n<p>既然我们已知具体的某条世界知识存储在某个或者某些 FFN 节点的参数里,自然会引发另外一个问题:我们能否修正 LLM 模型里存储的错误或者过时的知识呢?比如对于问题「英国的现任首相是谁?」鉴于近年来英国首相频繁更迭,你猜 LLM 更倾向输出「鲍里斯」还是更青睐「苏纳克」?很明显训练数据中包含“鲍里斯”的数据会更多,这种情况很大可能 LLM 会给出错误回答,于是我们就有修正 LLM 里存储的过时知识的必要性。</p>\n\n<p>如果归纳下,目前有三类不同方法来修正 LLM 里蕴含的知识:</p>\n\n<p>第一类方法从训练数据的源头来修正知识。<a href=\"https://arxiv.org/pdf/2205.11482.pdf\">《Towards Tracing Factual Knowledge in Language Models Back to the Training Data》</a>这篇文章的研究目标是:对于指定的某条知识,我们是否可以定位到是哪些训练数据导致 LLM 学会了这条知识?答案是肯定的,这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术,假设我们想要删除某条知识,则可首先定位到其对应的数据源头,删除数据源,然后重新预训练整个 LLM 模型,这样即可达成删除 LLM 中相关知识的目的。但是这里有个问题,如果修正一小部分知识,我们就需要重新做一次模型预训练,这样做明显成本太高。所以这种方法不会太有发展前景,可能比较适合那种对于某个特定类别数据的一次性大规模删除场合,不适合少量多次的常规知识修正场景,比如可能比较适合用来做去除偏见等去 toxic 内容的处理。</p>\n\n<p>第二类方法是对 LLM 模型做一次 fine-tuning 来修正知识。一个直观能想到的方法是:我们可以根据要修正成的新知识来构建训练数据,然后让 LLM 模型在这个训练数据上做 fine-tuning,这样指导 LLM 记住新的知识,遗忘旧的知识。这个方法简单直观,但是也有一些问题,首先它会带来灾难遗忘问题,就是说除了忘掉该忘的知识,还忘掉了不该忘的知识,导致这么做了之后有些下游任务效果下降。另外,因为目前的 LLM 模型规模非常大,即使是做 fine-tuning,如果次数频繁,其实成本也相当高。对这种方法感兴趣的可以参考<a href=\"https://arxiv.org/pdf/2012.00363.pdf\">《Modifying Memories in Transformer Models》</a>。</p>\n\n<p>另外一类方法直接修改 LLM 里某些知识对应的模型参数来修正知识。假设我们想要把旧知识 <英国,现任首相,鲍里斯>,修正到 <英国,现任首相,苏纳克>。首先我们想办法在 LLM 模型参数中,定位到存储旧知识的 FFN 节点,然后可以强行调整更改 FFN 中对应的模型参数,将旧知识替换成新的知识。可以看出,这种方法涉及到两项关键技术:首先是如何在 LLM 参数空间中定位某条知识的具体存储位置;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。理解这个修正 LLM 知识的过程,其实对于更深入理解 LLM 的内部运作机制是很有帮助的。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:如何修改已存储的知识?<br />\n首先是如何定位存哪了;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。</p>\n</blockquote>\n\n<h2 id=\"三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</h2>\n\n<p>我们知道,近年来,LLM 模型规模在快速增长,目前效果最好的 LLM 模型,其参数规模大都超过了千亿(100B)参数规模。比如,OpenAI 的 GPT 3 的规模为 175B,Google 的 LaMDA 规模为 137B,PaLM 的规模为 540B,DeepMind 的 Gogher 规模为 280B 等,不一而足。国内也有中文巨型模型,比如智源 GLM 规模 130B,华为「盘古」规模 200B,百度「文心」规模 260B,浪潮「源1.0」规模 245B。那么,一个很自然的问题就是:随着 LLM 模型规模不断增长,会发生些什么呢?</p>\n\n<p>预训练模型的应用往往是两阶段的:预训练阶段,及具体场景应用阶段。在预训练阶段,其优化目标是交叉熵,对 GPT 这种自回归语言模型来说,也就是看 LLM 是否正确预测到了下一个单词;而场景应用阶段,一般要看具体场景的评价指标。一般我们的直觉是:如果 LLM 模型在预训练阶段的指标越好,自然它解决下游任务的能力就越强。然而,事实并非完全如此。现有研究已证明,预训练阶段的优化指标确实和下游任务表现出正相关关系,但是并非完全正相关。也就是说,只看预训练阶段的指标,来判断一个 LLM 模型是否够好,这是不够的。基于此,我们分头来看在这两个不同阶段,随着 LLM 模型增大,有什么影响。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-6.jpeg\" alt=\"image\" /></p>\n\n<p>首先,我们先看在预训练阶段,随着模型规模逐步增大,会发生什么。OpenAI 在<a href=\"https://arxiv.org/pdf/2001.08361\">《Scaling Laws for Neural Language Models》</a>中专门研究了这个问题,并提出 LLM 模型所遵循的「伸缩法则(scaling law)」。如上图所示,这个研究证明:<strong>当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好</strong>。</p>\n\n<p>既然三个因素都重要,那么我们在实际做预训练的时候,就有一个算力如何分配的决策问题:假设用于训练 LLM 的算力总预算(比如多少 GPU 小时或者 GPU 天)给定,那么是应该多增加数据量、减少模型参数呢?还是说数据量和模型规模同时增加,减少训练步数呢?此消彼长,某个要素规模增长,就要降低其它因素的规模,以维持总算力不变,所以这里有各种可能的算力分配方案。最终 OpenAI 选择了同时增加训练数据量和模型参数,但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了:对于训练数据量和模型参数这两个要素,如果只单独增加其中某一个,这不是最好的选择,最好能按照一定比例同时增加两者,它的结论是优先增加模型参数,然后才是训练数据量。假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 5.5 倍的模型参数量,1.8 倍的训练数据量,此时模型效果最佳。</p>\n\n<p>DeepMind 的一项研究(参考<a href=\"https://arxiv.org/pdf/2203.15556\">《Training Compute-Optimal Large Language Models》</a>)更深入地探究了这个问题,其基本结论和 OpenAI 的结论差不多,比如确实需要同时增加训练数据量和模型参数,模型效果才会更好。而很多大模型在做预训练的时候,并没有考虑这一点,很多 LLM 大模型只是单调增加模型参数,而固定住了训练数据量,这个做法其实是不对的,限制了 LLM 模型的潜力。但是它修正了两者的比例关系,<strong>认为训练数据量和模型参数是同等重要的,也就是说,假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 3.3 倍的模型参数量,3.3 倍的训练数据量,这样模型效果才最好</strong>。</p>\n\n<p>这意味着:增加训练数据量的重要性,比我们之前所认为的,还要重要。基于这个认知,DeepMind 在设计 Chinchilla 模型时,在算力分配上选择了另外一种配置:对标数据量 300B、模型参数量 280B 的 Gopher 模型,Chinchilla 选择增加 4 倍的训练数据,但是将模型参数降低为 Gopher 的四分之一,大约为70B。但是无论预训练指标,还是很多下游任务指标,Chinchilla 效果都要优于规模更大的 Gopher。</p>\n\n<p>这带给我们如下启示:我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。缩小模型规模有很多好处,比如在应用的时候,推理速度会快很多等,无疑这是一个很有前途的 LLM 发展路线。</p>\n\n<p>以上是从预训练阶段来看模型规模的影响,如果从 LLM 解决下游具体任务效果的角度来看,随着模型规模增大,不同类型的任务有不同的表现,具体而言,有以下三类情况。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-7.jpeg\" alt=\"image\" /></p>\n\n<p>第一类任务完美体现了 LLM 模型的 scaling law,就是说随着模型规模逐步放大,任务的表现越来越好,如上图里的(a)图所示。这类任务通常符合如下共性:它们往往都是知识密集型任务,也就是说如果 LLM 模型包含的知识量越多,这类任务表现越好。而很多研究已经证明越大的 LLM 模型学习效率越高,也就是说相同训练数据量,模型越大任务效果越好,说明面对的即使是同样的一批训练数据,更大的 LLM 模型相对规模小一些的模型,从中学到了更多的知识。更何况一般情况下,在增大 LLM 模型参数的时候,往往会同步增加训练数据量,这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图,为何随着模型规模增大,这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务,其实都属于这种知识密集型任务,而很多任务在近两年获得了极大的效果提升,甚至超过了人类表现。很明显,这大概率是 LLM 模型的规模增长带来的,而非归功于某项具体的技术改进。</p>\n\n<p>第二类任务展现出 LLM 具备某种「<strong>涌现能力(Emergent Ability)</strong>」,如上图(b)所示。所谓「涌现能力」,指的是当模型参数规模未能达到某个阀值时,模型基本不具备解决此类任务的任何能力,体现为其性能和随机选择答案效果相当,但是当模型规模跨过阀值,LLM 模型对此类任务的效果就出现突然的性能增长。也就是说,模型规模是解锁(unlock)LLM 新能力的关键,随着模型规模越来越大,会逐渐解锁 LLM 越来越多的新能力。这是个很神奇的现象,因为它意味着如下让人对未来可报乐观预期的可能:或许很多任务,目前 LLM 还不能很好地解决,甚至站在现在这个时刻的我们看起来,LLM 完全没有能力解决这类任务,但因 LLM 具备「涌现能力」,所以如果我们继续推大模型,也许某一天它的这项能力就被突然解锁了。LLM 模型的规模增长会给我们带来意想不到的精彩礼物。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2206.04615\">《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》</a>这篇文章指出,这类<strong>体现出「涌现能力」的任务也有一些共性:这些任务一般由多步骤构成,要解决这些任务,往往需要先解决多个中间步骤,而逻辑推理能力在最终解决这类任务中发挥重要作用</strong>。思维链(Chain of Thought)Prompting 是典型的增强 LLM 推理能力的技术,能大幅提升此类任务的效果,关于 CoT 技术,在随后小节内容会做解释,此处暂不展开。</p>\n\n<p>问题是,为何 LLM 会出现这种「涌现能力」现象呢?上述文章以及<a href=\"https://arxiv.org/pdf/2206.07682\">《Emergent Abilities of Large Language Models》</a>给出了几个可能的解释:</p>\n\n<p>一种可能解释是<strong>有些任务的评价指标不够平滑</strong>。比如说有些生成任务的判断标准,它要求模型输出的字符串,要和标准答案完全匹配才算对,否则就是 0 分。所以,即使随着模型增大,其效果在逐步变好,体现为输出了更多的正确字符片段,但是因为没有完全对,只要有任何小错误都给 0 分,只有当模型足够大,输出片段全部正确才能得分。也就是说,因为指标不够平滑,所以不能体现 LLM 其实正在逐步改善任务效果这一现实,看起来就是「涌现能力」这种外在表现。</p>\n\n<p>另外一种可能的解释是:有些任务由若干中间步骤构成,随着模型规模增大,解决每个步骤的能力也在逐步增强,但是只要有一个中间步骤是错的,最终答案就是错的,于是也会导致这种表面的「涌现能力」现象。</p>\n\n<p>当然,<strong>上面的解释目前还都是猜想,至于为何 LLM 会出现这种现象,还需要进一步更深入的研究</strong>。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-8.jpeg\" alt=\"image\" /></p>\n\n<p>还有少部分任务,随着模型规模增长,任务的效果曲线展现出 U 形特性:随着模型规模逐渐变大,任务效果逐渐变差,但是当模型规模进一步增长,则效果开始越来越好,呈现出 U 形增长趋势,如上图所示的粉红色 PaLM 模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢?<a href=\"https://arxiv.org/pdf/2211.02011\">《Inverse scaling can become U-shaped》</a>这篇文章给出了一种解释:这些任务,内部其实隐含了两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。当模型规模小的时候,无法识别任意一种子任务,所以模型的表现跟随机选择答案差不多,当模型增长到中等规模的时候,主要执行的是干扰任务,所以对真正的任务效果有负面影响,体现为真正任务效果的下降,而当进一步增加模型规模,则 LLM 可以忽略干扰任务,执行真正的任务,体现为效果开始增长。</p>\n\n<p>对于那些随着模型规模增大,效果一直下降的任务,如果采用思维链(CoT)Prompting,则部分任务的表现转换为遵循 Scaling law,即模型规模越大效果越好,而其它任务则转换为U性增长曲线。这其实侧面说明了:此类任务应属于推理类型的任务,所以加入 CoT 后任务表现会发生质的变化。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:训练数据规模、模型参数规模和训练时长(步数),与最终 LLM 性能(loss 衡量)之间什么关系?<br />\n1、当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好。<br />\n2、训练数据量和模型参数是同等重要的。<br />\n3、我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么有些模型不遵循 scaling law?三类任务:<br />\n第一类完美遵循 scaling low。<br />\n第二类过了阈值后涌现。《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》和《Emergent Abilities of Large Language Models》认为是指标不平滑 or 中间步骤是错的。\n第三类 U 型,《Inverse scaling can become U-shaped》猜测可能两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。</p>\n</blockquote>\n\n<h2 id=\"四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</h2>\n\n<p>一般我们经常提到的人和 LLM 的接口技术包括:zero shot prompting、few shot prompting、In Context Learning,以及 Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献,会发现叫法比较乱。</p>\n\n<p>其中 Instruct 是 ChatGPT 的接口方式,就是说人以自然语言给出任务的描述,比如「把这个句子从中文翻译成英文」,类似这种。zero shot prompting 我理解其实就是现在的 Instruct 的早期叫法,以前大家习惯叫 zero shot,现在很多改成叫 Instruct。尽管是一个内涵,但是具体做法是两种做法。早期大家做 zero shot prompting,实际上就是不知道怎么表达一个任务才好,于是就换不同的单词或者句子,反复在尝试好的任务表达方式,这种做法目前已经被证明是在拟合训练数据的分布,其实没啥意思。目前 Instruct 的做法则是给定命令表述语句,试图让 LLM 理解它。所以尽管表面都是任务的表述,但是思路是不同的。</p>\n\n<p>而In Context Learning 和 few shot prompting 意思类似,就是给 LLM 几个示例作为范本,然后让LLM解决新问题。我个人认为 In Context Learning 也可以理解为某项任务的描述,只是 Instruct 是一种抽象的描述方式,In Context Learning 是一种例子示范的例子说明法。当然,鉴于目前这几个叫法用的有点乱,所以上述理解仅代表个人看法。</p>\n\n<p>所以我们此处只对 In Context Learning 和 Instruct 进行介绍,不再提 zero shot 和 few shot 了。</p>\n\n<h3 id=\"1神秘的-in-context-learning\">1、神秘的 In Context Learning</h3>\n\n<p>如果你细想,会发现 In Context Learning 是个很神奇的技术。它神奇在哪里呢?神奇在你提供给 LLM 几个样本示例,….,然后给它 x(n+1),LLM 竟然能够成功预测对应的 y(n+1)。听到这你会反问:这有什么神奇的呢?Fine-tuning 不就是这样工作的吗?你要这么问的话,说明你对这个问题想得还不够深入。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-9.jpeg\" alt=\"image\" /></p>\n\n<p>Fine-tuning 和 In Context Learning 表面看似都提供了一些例子给 LLM,但两者有质的不同(参考上图示意):Fine-tuning 拿这些例子当作训练数据,利用反向传播去修正 LLM 的模型参数,而修正模型参数这个动作,确实体现了 LLM 从这些例子学习的过程。但是,In Context Learning 只是拿出例子让 LLM 看了一眼,并没有根据例子,用反向传播去修正 LLM 模型参数的动作,就要求它去预测新例子。既然没有修正模型参数,这意味着貌似 LLM 并未经历一个学习过程,如果没有经历学习过程,那它为何能够做到仅看一眼,就能预测对新例子呢?这正是 In Context Learning 的神奇之处。这是否让你想起了一句歌词「只是因为在人群中多看了你一眼 再也没能忘掉你容颜」,而这首歌名叫「传奇」。你说传奇不传奇?</p>\n\n<p>看似 In Context Learning 没从例子里学习知识,实际上,难道 LLM 通过一种奇怪的方式去学习?还是说,它确实也没学啥?关于这个问题的答案,目前仍是未解之谜。现有一些研究各有各的说法,五花八门,很难判断哪个讲述的是事实的真相,甚至有些研究结论还相互矛盾。这里提供几个目前的说法,至于谁对谁错,只能你自己把握了。当然,我认为追求这个神奇现象背后的真相,是一个好的研究课题。</p>\n\n<p>试图证明 In Context Learning 没有从例子中学习的工作是<a href=\"https://arxiv.org/pdf/2202.12837\">《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》</a>。它发现了:在提供给 LLM 的样本示例中,yi 是否 xi 对应的正确答案,其实并不重要,如果我们把正确答案 yi 替换成随机的另外一个答案 yj ,这并不影响 In Context Learning 的效果。这起码说明了一点:In Context Learning 并没有提供给 LLM 那个从 x 映射到 y 的映射函数信息:y=f(x),否则的话你乱换正确标签,肯定会扰乱这个 y=f(x) 映射函数。也就是说,In Context Learning 并未学习这个输入空间到输出空间的映射过程。</p>\n\n<p>真正对 In Context Learning 影响比较大的是:x 和 y 的分布,也就是输入文本 x 的分布和候选答案 y 有哪些,如果你改变这两个分布,比如把 y 替换成候选答案之外的内容,则 In Context Learning 效果急剧下降。</p>\n\n<p>总之,这个工作证明了 In Context Learning 并未学习映射函数,但是输入和输出的分布很重要,这两个不能乱改。</p>\n\n<p>有些工作认为 LLM 还是从给出的示例学习了这个映射函数 y=f(x),不过是种隐式地学习。比如<a href=\"https://arxiv.org/pdf/2211.15661.pdf\">《What learning algorithm is in-context learning? Investigations with linear models》</a>认为 Transformer 能够隐式地从示例中学习 x 到 y 的映射过程,它的激活函数中包含了一些简单映射函数,而 LLM 通过示例能够激发对应的那一个。而<a href=\"https://arxiv.org/pdf/2212.10559\">《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》</a>这篇文章则将 ICL 看作是一种隐式的 Fine-tuning。</p>\n\n<p>总而言之,<strong>目前这还是一个未解之谜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】LLM 技术增量重点</strong>:In Context Learning</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:为什么 In Context Learning 有效?<br />\n1、《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》认为 ICL 没有从例子里学习 x 到 y 的映射关系,而只是学习了分布与分布的对应。<br />\n2、《What learning algorithm is in-context learning? Investigations with linear models》认为 ICL 隐式地学习了 x 到 y 的映射关系。<br />\n3、《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》认为 ICL 是隐式的 fine-tuning。</p>\n</blockquote>\n\n<h3 id=\"2神奇的-instruct-理解\">2、神奇的 Instruct 理解</h3>\n\n<p>我们可以把 Instruct 当作一种方便人类理解的任务表述,在这个前提下,目前关于 Instruct 的研究可以分成两种:偏学术研究的 Instruct,以及关于人类真实需求描述的 Instruct。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-10.jpeg\" alt=\"image\" /></p>\n\n<p>我们先来看第一种:偏学术研究的 Instruct。它的核心研究主题是多任务场景下,LLM 模型对 Instruct 理解的泛化能力。如上图中 FLAN 模型所示,就是说有很多 NLP 任务,对于每个任务,研究人员构造一个或者多个 Prompt 模版作为任务的 Instruct,然后用训练例子对 LLM 模型进行微调,让 LLM 以同时学习多个任务。训练好模型后,给 LLM 模型一个它没见过的全新任务的 Instruct,然后让 LLM 解决 zero shot 任务,从任务解决得是否足够好,来判断 LLM 模型是否有对 Instruct 理解的泛化能力。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】FLAN 是什么?</strong>2021 年 9 月 Google Research 团队在文章<a href=\"https://arxiv.org/pdf/2109.01652\">《Finetuned Language Models Are Zero-Shot Learners》</a>中提出的「Finetuned Language 」<br />\n1、FLAN 是 Finetuned LAnguage Net 的缩写,它用 Multi-task Learning 的方法和一种别出心裁的微调方式对 PLM 进行微调,在参数少 400 亿的情况下,性能超越 GPT-3。<br />\n2、部分参考自:https://juejin.cn/post/7064919723498012703 <br />\n3、FLAN 训练:对多个任务,把 Prompt 模板写成 Instruct,以此微调来学习。<br />\n4、FLAN 测试:新类型的任务,直接 zero-shot,判断 LLM 对 Instruct 理解的泛化能力。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】如何增加 LLM 模型 Instruct 泛化能力</strong> <br />\n1、增加训练任务的数量 <br />\n2、增加训练任务的类型多样性 <br />\n3、增加 LLM 模型参数规模 <br />\n4、提供 CoT Prompting</p>\n</blockquote>\n\n<p>如果归纳下目前的研究结论(可参考<a href=\"https://arxiv.org/pdf/2210.11416\">《Scaling Instruction-Finetuned Language Models》</a>/<a href=\"https://arxiv.org/pdf/2204.07705\">《Super-Natural Instructions: Generalization via Declarative Instructions on 1600+ NLP Tasks》</a>),能够有效增加 LLM 模型 Instruct 泛化能力的因素包括:增加多任务的任务数量、增加 LLM 模型大小、提供 CoT Prompting, 以及增加任务的多样性。如果采取任意一项措施,都可以增加 LLM 模型的 Instruct 理解能力。</p>\n\n<p>第二种是人类真实需求下的 Instruct,这类研究以 InstructGPT 和 ChatGPT 为代表。这类工作也是基于多任务的,但是和偏向学术研究类工作最大的不同,在于它是面向人类用户真实需求的。为什么这么说呢?因为它们用于 LLM 多任务训练的任务描述 Prompt,是从大量用户提交的真实请求中抽样而来的,而不是固定好研究任务的范围,然后让研究人员来写任务描述 prompt。这里所谓的「真实需求」,体现在两个方面:首先,因为是从用户提交的任务描述里随机抽取的,所以涵盖的任务类型更多样化,也更符合用户的真实需求;其次,某个任务的 prompt 描述,是用户提交的,体现了一般用户在表达任务需求时会怎么说,而不是你认为用户会怎么说。很明显,这类工作改出来的 LLM 模型,用户体验会更好。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2203.02155.pdf\">InstructGPT 论文</a>里,也拿这种方法和 FLAN 那种 Instruct based 方法做了比较。首先在 GPT3 上用 FLAN 提到的任务、数据以及 Prompt 模版进行微调,来在 GPT 3 上复现 FLAN 方法,然后和 InstructGPT 进行比较,因为 InstructGPT 的基础模型也是 GPT3,所以只有数据和方法的差别,两者可比,结果发现 FLAN 方法的效果,距离 InstructGPT 有很大的差距。那么背后的原因是什么呢?论文分析数据后认为,<strong>FLAN 方法涉及到的任务领域相对少</strong>,是 InstructGPT 涉及领域的子集,所以效果不好。也就是说,<strong>FLAN 论文里涉及到的任务和用户真实需求是不符的</strong>,而这导致在真实场景下效果不够好。而这对我们的启示是:从用户数据中收集真实需求,这事情是很重要的。</p>\n\n<blockquote>\n <p><strong>LLM 技术增量重点</strong>:Instruct <br />\n1、FLAN 的任务领域太少。<br />\n2、FLAN 不是从用户数据收集真实需求(研究人员构造任务),与用户真实需求不符。</p>\n</blockquote>\n\n<h3 id=\"3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</h3>\n\n<p>如果我们假设 In Context Learning 是用一些例子来具象地表达任务命令,Instruct 是一种更符合人类习惯的抽象任务描述。那么,一个很自然的问题是:它们之间有什么联系吗?比如,我们是否能够提供给 LLM 完成某个任务的若干具体示例,让 LLM 找出其对应的自然语言描述的 Instruct 命令?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-11.jpeg\" alt=\"image\" /></p>\n\n<p>目前有零星的工作在探索这个问题,我认为这个方向是很有研究价值的。先说答案,答案是:Yes,LLM Can。<a href=\"https://arxiv.org/pdf/2211.01910\">《Large Language Models Are Human-Level Prompt Engineers》</a>是做这个方向很有趣的工作,如上图所示,对于某项任务,给 LLM 一些示例,让 LLM 自动生成能够描述这项任务的自然语言命令,然后它再用 LLM 生成的任务描述去测试任务效果。它使用的基础模型是 GPT 3 和 InstructGPT,经过这项技术加持后,LLM 生成的 Instruct 的效果相比未采用这项技术的 GPT 3 以及 InstuctGPT 来说,指标有极大地提升,而且在一些任务上超过人类的表现。</p>\n\n<p>这说明了:<strong>具象的任务示例和任务的自然语言描述之间,有种神秘的内在联系。至于这种联系到底是什么?我们目前对此还一无所知</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:Instruct 和 ICL 之间的联系 <br />\n1、ICL 是给了一些具象例子的命令 <br />\n2、Instruct 相当于是抽象命令</p>\n</blockquote>\n\n<h2 id=\"五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</h2>\n\n<p>目前很多研究已证明 LLM 对于知识具有强大的记忆能力,但是,一般我们不会因为一个人记忆能力强,就说这人很聪明,是否具有强大的推理能力,往往是我们判断一个人是否聪明的重要标准。类似的,如果 LLM 的效果想让人觉得很惊艳,强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点,去推导出新知识或新结论。关于 LLM 的推理能力,是最近一年来 LLM 里最重要和热门的研究领域之一。于是,我们关心的问题就是:<strong>LLM 具备推理能力吗?如果具备,那么它的推理能力够强吗?</strong></p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:LLM 具备推理能力吗?</p>\n</blockquote>\n\n<p>这两个问题目前的答案似乎应该是:当模型规模足够大的时候,LLM 本身是具备推理能力的,在简单推理问题上,LLM 已经达到了很好的能力,但是复杂推理问题上,还需要更多深入的研究。</p>\n\n<p>如果梳理现有 LLM 推理相关工作的话,我把它们归到两大类,体现出挖掘或促进 LLM 推理能力不同的技术思路:第一类研究比较多,可以统称为<strong>基于 Prompt 的方法</strong>,核心思想是通过合适的提示语或提示样本,更好地激发出 LLM 本身就具备的推理能力,Google 在这个方向做了大量很有成效的工作。第二类做法是在<strong>预训练过程中引入程序代码</strong>,和文本一起参与预训练,以此进一步增强 LLM 的推理能力,这应该是 OpenAI 实践出的思路。比如 ChatGPT 肯定具备很强的推理能力,但它并不要求用户必须提供一些推理示例,所以 <strong>ChatGPT 强大的推理能力,大概率来源于使用代码参与 GPT 3.5 的预训练</strong>。</p>\n\n<p>这两种思路其实大方向是迥异的:利用代码增强 LLM 推理能力,这体现出一种通过增加多样性的训练数据,来直接增强 LLM 推理能力的思路;而基于 Prompt 的方法,它并不会促进 LLM 本身的推理能力,只是让 LLM 在解决问题过程中更好地展示出这种能力的技术方法。可以看出,前者(代码方法)治本,后者治标。当然,两者其实也是互补的,但从长远看,治本的方法更重要。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】挖掘或促进 LLM 推理能力的两个技术思路</strong>:<br />\n1、Google 有大量研究成果的基于 Prompt 的方法:对应 ICL,挖掘 LLM 的推理能力 —— 基于神奇的 ICL <br />\n2、OpenAI 实践出真知的策略 —— Pre-training 时引入程序代码</p>\n</blockquote>\n\n<h3 id=\"1基于-prompt-的方法\">1、基于 Prompt 的方法</h3>\n\n<p>这方面工作非常多,如果归纳一下的话,大致可以分为三条技术路线。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-12.jpeg\" alt=\"image\" /></p>\n\n<p>第一种思路是直接在问题上追加辅助推理 Prompt。这种方法简单直接,但在众多领域都很有效。这个做法是由<a href=\"https://arxiv.org/pdf/2205.11916\">《Large language models are zero-shot reasoners》</a>提出的,也被称为 zero-shot CoT。具体而言,分为两个阶段(如上图所示),第一阶段在提问的问题上追加「Let’s think step by step」这句提示语,LLM 会输出具体的推理过程;第二阶段,在第一阶段的问题后,拼接 LLM 输出的具体推理过程,并再追加 Prompt=“Therefore, the answer (arabic numerals) is”,此时 LLM 会给出答案。如此简单的操作,却可以大幅增加 LLM 在各项推理任务中的效果,比如在数学推理测试集 GSM8K 上,加上提示语后,推理准确率直接从原先的 10.4% 提升到了 40.4%,可谓神奇。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>加上「Let’s think step by step」立马就提升了 GSM8K 数学推理测试集的准确率 +30pt!</p>\n</blockquote>\n\n<p>为什么 LLM 会具备给一句「Let’s think step by step」提示语,就能列出详细的推理步骤并算出答案呢?其原因目前尚无定论,我的猜测是:<strong>很可能因为预训练数据里面存在大量的此种数据,就是以「Let’s think step by step」开头,然后后面是详细的推理步骤,最后给出答案,而 LLM 在预训练的时候记住了这些模式。而当我们输入这个提示语的时候,激发 LLM 模糊得「回忆」起某些例子的推导步骤,于是即可模仿这些例子进行步骤推理并给出答案</strong>。当然这只是我的无依据推论,若事实真的如此,如果你看过后面介绍的标准 CoT 做法,会发现 Zero-shot CoT 本质上和标准 CoT 很可能没什么区别,只是标准 CoT 由人工来写推理步骤的示例,而 Zero-shot CoT 大概率是通过提示语,激活了记忆中的某些包含推理步骤的示例,很可能是如此区别。而标准 CoT 效果比 Zero-Shot CoT 效果好也完全可以理解,因为毕竟靠 LLM 回忆示例,精准性估计不会太高,而人工给出的示例,准确性是有保障的,所以自然标准 CoT 效果会更好。</p>\n\n<p><strong>这侧面说明了一个道理,就是 LLM 本身是具备推理能力的</strong>,只是我们没有办法把它的这种能力激发出来而已,通过合适的提示语来进行两步提示,就在一定程度上可以释放出它的这种潜力。另外,对于中文,很可能存在另外一个黄金提示语,比如「详细解题思路如下」,类似这种,因为中文语料在讲解推理步骤的时候,经常用的引导句和「让我们一步一步来思考」应该是不同的,这是明显的西方说法,而探索出这个中文黄金提示语,其实也是很有必要的。</p>\n\n<p>第二种思路一般被称为基于示例的思维链(few-shot CoT, Chain of Thought)Prompting。这个方向目前是 LLM 推理研究的主方向,很多工作都是在这个思路上做的,我们简单介绍几个效果显著的代表性工作,基本能代表 CoT 的技术发展方向。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-13.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 的主体思想其实很直白;为了教会 LLM 模型学会推理,给出一些人工写好的推理示例,示例里把得到最终答案前,一步步的具体推理步骤说清楚,而这些人工写的详细推理过程,就是思维链 Prompting,具体例子可参照上图中蓝色文字部分。CoT 的意思是让 LLM 模型明白一个道理;<strong>就是在推理过程中,步子不要迈得太大,否则很容易出错,改变思维模式,化大问题为小问题,步步为营,积小胜为大胜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>这个过程已经从 programming computer 过渡成 teaching computer 了,训练 AI 越来越像一个需要人教育培养的孩子。<br />\nCoT 其实就是给一些思维链 step by step 的 prompting</p>\n</blockquote>\n\n<p>最早明确提出 CoT 这个概念的文章是<a href=\"https://arxiv.org/pdf/2201.11903\">《Chain of thought prompting elicits reasoning in large language models》</a>,论文发布于 22 年 1 月份,虽然做法很简单,但是应用 CoT 后 LLM 模型的推理能力得到了巨大提升,GSM8K 数学推理测试集准确率提高到 60.1% 左右。当然,这种给出详细推理步骤和中间过程的思想,并非 CoT 最早提出的,更早一些的「scratchpad」技术(可参考<a href=\"https://arxiv.org/pdf/2112.00114\">《Show Your Work: Scratchpads for Intermediate Computation with Language Models》</a>)首先采用了类似的思路。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-14.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 提出不久,很快在 22 年 3 月份,一项被称为「Self-Consistency」的改进技术就将 GSM8K 测试集准确率提高到 74.4%,提出这项改进的论文是<a href=\"https://arxiv.org/pdf/2203.11171\">《Self-Consistency Improves Chain of Thought Reasoning in Language Models》</a>。「Self-Consistency」的思路也很直观(参考上图):首先可以利用 CoT 给出几个写了推理过程的示例,然后要求 LLM 对给定的问题进行推理,如果是 CoT,直接输出一个推理过程和答案,整个过程就结束了。「Self-Consistency」则不然,它要求 LLM 输出多个不同的推理过程和答案,然后采用投票的方式选出最佳答案,思路非常简单直接,但是效果也确实好。「Self-Consistency」其实是教导 LLM 学会这么一个道理:孔乙己说过茴香豆的「茴」字有四种写法,类似的,一个数学题的正确解法也可以有很多种,每个不同的推导过程都指向最终的答案。条条大路通罗马,虽说也有个别迷路走到北京的,但是迷路的毕竟是少数,看看大多数人走到哪里,哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义,是不是这道理?</p>\n\n<p>再往后,<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>这个工作在「Self-Consistency」基础上,进一步集成了「从一个 Prompt 问题拓展到多个 Prompt 问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票」这三个改进点,将 GSM8K 测试集准确率提高到 83% 左右。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>GSM8K 数学推理测试集 <br />\n1、提高到 40.4%:加一句「Let’s think step by step」 <br />\n2、提高到 60.1%:应用 CoT 后,即训练时给 LLM 几个写了推理过程的示例 <br />\n3、提高到 74.7%:基于 CoT 的改进技术 Self-Consistency,给出多个不同推理过程和答案,投票选出最好答案 <br />\n4、提高到 83% 左右:基于 Self-Constistenty 的改进技术,1)一个 Prompt 拓展为多个 Prompt;2)检查推理中间步骤正确性;3)对多个输出的回答加权投票。</p>\n</blockquote>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-15.jpeg\" alt=\"image\" /></p>\n\n<p>第三种思路体现了一种分治算法的思想。当然这个所谓「分治」是我归纳的,别人没这么说。这种思路的核心思想是:对于一个复杂的推理问题,我们把它分解成若干容易解决的子问题,一一解决掉子问题后,我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得,这种思路可能才是揭示问题本质、最终解决 LLM 复杂推理问题正宗的道路。我们以「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术为例来说明这种思路的一种具体实现方式,如上图所示:它分为两个阶段,第一个阶段,从原始问题我们可以得知最终要问的问题是什么,我们假设最终问题是 Final Q,然后从原始问题填充 Prompt 模版「如果要解决 Final Q 问题,那么我需要先解决」,然后把原始问题和这个 Prompt 交给 LLM,让 LLM 模型给出答案,等于让 LLM 给出最终问题的前置子问题 Sub Q;接下来我们进入第二个阶段,让 LLM 先回答刚才拿到的子问题Sub Q,并拿到对应的答案,然后原始问题拼接子问题 Sub Q 及对应答案,再去问 LLM 最终那个问题 Final Q,此时LLM会给出最后的答案。如此这般,体现出拆解子问题,并从子问题的答案逐步找出最终答案的思路。</p>\n\n<h3 id=\"2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</h3>\n\n<p>以上是目前利用 Prompt 激发 LLM 模型推理能力的三种主流做法,而关于 LLM 的推理能力,目前还观察到一个有趣且费解的现象:除了文本外,如果能够加入程序代码一起参与模型预训练,则能大幅提升 LLM 模型的推理能力。这个结论从不少论文的实验部分都可以得出(可以参考<a href=\"https://arxiv.org/pdf/2210.03493\">《AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS》</a>/<a href=\"https://arxiv.org/pdf/2210.09261\">《Challenging BIG-Bench tasks and whether chain-of-thought can solve them》</a>等论文的实验部分)。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-16.jpeg\" alt=\"image\" /></p>\n\n<p>上图给出了一份实验数据,来自于论文<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>,其中 GPT3 davinci 就是标准的 GPT 3 模型,基于纯文本训练;code-davinci-002(OpenAI 内部称为 Codex)是同时在 Code 和 NLP 数据上训练的模型。如果比较两者效果,可以看出,不论采用具体哪种推理方法,仅仅是从纯文本预训练模型切换到文本和 Code 混合预训练模型,在几乎所有测试数据集合上,模型推理能力都得到了巨大的效果提升,比如我们以「Self Consistency」方法为例,在大多数据集合上的性能提升,都直接超过了 20 到 50 个百分点,这是很恐怖的性能提升,而其实在具体推理模型层面,我们什么也没做,仅仅是预训练的时候除了文本,额外加入了程序代码而已。</p>\n\n<p>除了这个现象,从上图数据中,我们还可以得出其它一些结论,比如 GPT 3 这种纯文本预训练模型,其实是具备相当程度的推理能力的,除了在 GSM8K 这种数学推理上效果比较差外,其它推理数据数据集合表现也还可以,前提你需要采用合适的方法,来激发出它本身就具备的这种能力;再比如,text-davinci-002,也就是在 code-davinci-002 基础上加入 instruct fine-tuning 后的模型(就是加入 InstructGPT 或 ChatGPT 模型的第一步),其推理能力要弱于 Codex,但是有其它研究表明它在自然语言处理任务又要强于 Codex。而这貌似说明了,加入 instruct fine-tuning,会损害 LLM 模型的推理能力,但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的,也能启发后续进一步的思考和探索。</p>\n\n<p>那么,一个自然的疑问是:<strong>为何预训练模型可以从代码的预训练中获得额外的推理能力?确切原因目前未知,值得深入探索</strong>。我猜测可能是因为原始版本的 Codex(只使用代码训练,可参考文献:<a href=\"https://arxiv.org/pdf/2107.03374\">《Evaluating Large Language Models Trained on Code》</a>)的代码训练是从文本生成代码,而且代码中往往包含很多文本注释,本质上这类似于预训练模型做了 <文本,Code> 两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释,很明显这些数学类或逻辑推理类的数据,对于解决下游数学推理问题是有帮助的,我猜大概率原因在此。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么预训练模型可以从代码预训练中获得额外的推力能力?</p>\n</blockquote>\n\n<h3 id=\"3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</h3>\n\n<p>上面介绍了 LLM 推理的主流技术思路和现有的一些结论,接下来谈谈我对 LLM 模型推理技术的思考,以下内容纯个人推断,没有太多证据,还请谨慎参考。我的判断是:虽然最近一年来,关于激发 LLM 的推理能力,这方面的技术进展很快,也取得了很大的技术进步,但是总体感觉是,我们可能走在正确的方向上,但是距离接触到真正的问题本质还有一段距离,对此要有更深入的思考和探索。</p>\n\n<p>首先,我比较赞同上述分治算法的主体思路,对于复杂的推理问题,我们应该把它拆解成若干简单的子问题,因为子问题对于 LLM 来说回答正确的概率就大很多,让 LLM 一一 回答子问题后,再逐步推导出最终答案。受到「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术的启发,如果进一步思考,我觉得 LLM 推理本质上很可能会是如下两种可能的其中之一:不断和 LLM 进行交互的图上推理问题,抑或是不断和LLM进行交互的程序流程图执行问题。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-17.jpeg\" alt=\"image\" /></p>\n\n<p>先说图上推理问题,如上图所示,假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构,图中的节点是子问题或者子步骤,图中的边代表了子问题之间的依赖关系,就是说只有回答好子问题 A,才能回答子问题 B,而且图中大概率存在循环结构,就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图,那么可以根据依赖关系,引导 LLM 一步一步按照图结构,回答必须首先回答的子问题,直到推导出最终答案。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-18.jpeg\" alt=\"image\" /></p>\n\n<p>再说程序流程图问题,参考上图,假设我们有办法把复杂问题拆解成子问题或子步骤,并产生一个由子步骤构成的类似程序流程图的结构,在这个结构里,有些步骤会反复执行多次(循环结构),有些步骤的执行需要进行条件判断(条件分支)。总而言之,在执行每个子步骤的时候和 LLM 进行交互,得到子步骤的答案,然后按照流程不断执行,直到输出最终答案。类似这种模式。假设这个思路大致正确的话,也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力:大概率因为 <文本,代码> 的多模态预训练模型,在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁,将两者联系起来的,即由文本描述到隐含的流程图,再映射到由流程图产生具体的代码。也就是说,这种多模态预训练,可以增强 LLM 模型从文本构建出隐含的流程图并按照流程图执行的能力,也就是加强了它的推理能力。</p>\n\n<p>当然,上述思路最大的问题是,我们如何根据文本描述的问题,能够靠 LLM 模型,或者其它模型,得到图结构或者流程图结构?这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练,走隐式学习内部隐含结构的方法。而目前的 CoT 技术,如果套到上述思路来思考的话,可以这么理解:标准 CoT,其实就是靠自然语言文本来描述图结构或者程序流程图的;而「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术,则是试图根据最后一个图节点,靠倒推来试图推导出其中的图结构,但是很明显,目前的方法限制了它倒推的深度,也就是说它只能推导出非常简单的图结构,这正是限制它能力的所在。</p>\n\n<h2 id=\"六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</h2>\n\n<p>这里列出一些我个人认为比较重要的 LLM 研究领域,或值得深入探索的研究方向。</p>\n\n<h4 id=\"探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</h4>\n\n<p>尽管继续推大 LLM 模型的规模,这事看似没有技术含量,但是其实这个事情异常重要。我个人判断,自从 Bert 出现以来,到 GPT 3,再到 ChatGPT,大概率这些给人印象深刻的关键技术突破,核心贡献都来自于 LLM 模型规模的增长,而非某项具体技术。说不定,揭开 AGI 真正的钥匙就是:超大规模及足够多样性的数据、超大规模的模型,以及充分的训练过程。再者,做超大规模的 LLM 模型,对技术团队的工程实现能力要求是非常高的,也不能认为这事情缺乏技术含量。</p>\n\n<p>那么继续推大 LLM 模型规模,有什么研究意义呢?我觉得有两方面的价值。首先,如上所述,我们已知,对于知识密集型的任务,随着模型规模越大,各种任务的效果会越来越好;而对很多推理类型的有难度的任务,加上 CoT Prompting 后,其效果也呈现出遵循 Scaling law 的趋向。那么,很自然的一个问题就是:对于这些任务,LLM 的规模效应,能将这些任务解决到何种程度?这是包括我在内,很多人关心的问题。其次,考虑到 LLM 具备的神奇的「涌现能力」,如果我们继续增加模型规模,它会解锁哪些让我们意想不到的新能力呢?这也是很有意思的问题。考虑到以上两点,我们仍然需要不断增大模型规模,看看模型规模对解决各类任务的天花板在哪里。</p>\n\n<p>当然,这种事情也就只能说说,对 99.99% 的从业者来说,是没有机会和能力做这个事情的。要做这个事情,对研究机构的财力及投入意愿、工程能力、技术热情,都有极高的要求,缺一不可。能做这事情的机构,粗估下来,国外不超过 5 家,国内不超过 3 家。当然,考虑到成本问题,未来也许会出现「股份制大模型」,就是有能力的几家机构合作,群策群力,一起来共建超级大模型的现象。</p>\n\n<h4 id=\"增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</h4>\n\n<p>正如之前对 LLM 推理能力的叙述,尽管 LLM 在最近一年推理能力得到了很大的提升,但是很多研究(参考<a href=\"https://arxiv.org/pdf/2208.05051\">《Limitations of Language Models in Arithmetic and Symbolic Induction》</a>/<a href=\"https://arxiv.org/pdf/2206.10498\">《Large Language Models Still Can’t Plan》</a>)表明,目前 LLM 能够解决得比较好的推理问题,往往都相对简单,LLM 的复杂推理能力仍然薄弱,比如即使是简单的字符拷贝推理或者加减乘除运算,当字符串或者数字非常长的时候,LLM 推理能力会极速下降,再比如行为规划能力等复杂推理能力很弱。总而言之,加强 LLM 的复杂推理能力,应该是 LLM 未来研究中最重要的环节之一。</p>\n\n<p>前文有述,<strong>加入代码加入预训练,这是一种直接增强 LLM 推理能力的方向。这个方向目前研究尚显不足,更像是实践经验的总结</strong>,探索背后的原理,并进而引入更多类型除代码外的新型数据来增强 LLM 的推理能力,这可能是更本质提升推理能力的方向。</p>\n\n<h4 id=\"llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</h4>\n\n<p>目前的 ChatGPT 擅长 NLP 和 Code 任务,作为通向 AGI 的重要种子选手,<strong>将图像、视频、音频等图像与多模态集成进入 LLM,乃至 AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入 LLM</strong>,是 LLM 通往 AGI 的必经之路。而这个方向才刚刚开始,因此具备<strong>很高的研究价值</strong>。</p>\n\n<h4 id=\"更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</h4>\n\n<p>如前所述,ChatGPT 的最大技术贡献即在此。但是很明显,目前的技术并不完美,肯定还有很多命令 LLM 理解不了。所以,沿着这个方向,寻找更好的技术,<strong>来让人类使用自己习惯的命令表达方式,而 LLM 又能听懂,这是个新的,且非常有前景的技术方向</strong>。</p>\n\n<h4 id=\"建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</h4>\n\n<p>好的评测数据集,是引导技术不断进步的基石。随着 LLM 模型逐步增大,任务效果快速提升,导致很多标准测试集快速过时。也就是说,这些数据集合相对现有技术来说,太容易了,在没有难度的测试集合下,我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合,是促进 LLM 技术进步的关键所在。</p>\n\n<p>目前行业应出现了一些新的测试集,有代表性的包括 BIGBench、OPT-IML 等。这些测试集合体现出一些特性,比如相对 LLM 现有技术具备一定的难度、综合了各种各样多种类型的任务等。</p>\n\n<p>受到 ChatGPT 的启发,我觉得除此外应纳入另一考虑因素:体现真实用户需求。就是说,这些任务的表述由用户真实发起,这种方式构建出来的 LLM 模型,才能解决用户实际需求。</p>\n\n<p>除此外,相信 LLM 会快速将能力溢出到 NLP 之外的领域,而如何融入更多其它领域的评测数据,也是需要提前去考虑。</p>\n\n<h4 id=\"高质量数据工程\">高质量数据工程</h4>\n\n<p>对于预训练模型来说,数据是其根本,预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此,我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。</p>\n\n<p>关于数据,需要考虑两个方面:数据的质量和数量。而根据 T5 的对比实验,我们可以得出结论:在数量和质量两个因素里,质量优先,正确的道路应该是在保证数据质量的前提下,再去增大数据规模。</p>\n\n<p>数据质量,包括数据的信息含量以及数据的多样性等多个衡量标准,比如 Wiki 明显就属于世界知识密度极高的高质量数据,这是从信息含量来说的;而增加数据类型的多样性,无疑是激发 LLM 各种新能力的根本,比如加入问答网站的数据,对于 LLM 的 QA 能力提升是有直接帮助的。多样化的数据赋予了 LLM 更好解决更多不同类型任务的能力,所以,这可能是数据质量里最关键的标准。</p>\n\n<p>关于数据数量,原则上互联网上公开发布的数据都可以纳入 LLM 模型的预训练过程。那么,它的极限在哪里?<a href=\"https://arxiv.org/pdf/2211.04325\">《Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning》</a>对此进行了估算,结论是到 2026 年左右,高质量的NLP数据将会用光,低质量 NLP 数据会在 2030 到 2050 年用光,而低质量图像数据会在 2030 到 2060 年用光。而这意味着:要么到时我们有新类型的数据源,要么我们必须增加 LLM 模型对数据的利用效率。否则,目前这种数据驱动的模型优化方式将会停止进步,或者收益减少。</p>\n\n<h4 id=\"超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</h4>\n\n<p>目前规模最大的 LLM 中,有相当比例的模型采取了稀疏(Sparse)结构,比如 GPT 3、PaLM、GLaM 等,GPT 4 大概率也会走稀疏模型路线。之所以采用 Sparse 化的模型,主要好处是它可以极大减少 LLM 的训练时间和在线推理时间。Switch Transformer 论文里指出:在相同算力预算的前提下,使用稀疏化 Transformer,相对 Dense Transformer,LLM 模型的训练速度可以提升 4 倍到 7 倍。为何 Sparse 模型可以加快训练和推理时间呢?这是因为尽管模型参数巨大,但是对于某个训练实例,Sparse 模型通过路由机制,只使用整个参数中的一小部分,参与训练和推理的活跃参数量比较少,所以速度快。</p>\n\n<p>我认为未来超大的 LLM 模型大概率会收敛到稀疏模型。主要有两个原因:一方面,现有研究表明(参考<a href=\"https://arxiv.org/pdf/2210.06313\">《Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers》</a>),标准的 Dense Transformer在训练和推理时,它本身也是稀疏激活的,就是说只有部分参数会被激活,大部分参数没有参与训练和推理过程。既然这样,我们不如直接迁移到稀疏模型;另外,毫无疑问 LLM 模型的规模会继续推大,而高昂的训练成本是妨碍其进一步扩大模型的重要阻力,<strong>使用稀疏模型可以极大降低超大模型的训练成本</strong>,所以随着模型规模越大,稀疏模型带来的收益越明显。考虑到这两个方面,大概率未来更大的 LLM 模型会采用稀疏模型方案。</p>\n\n<p>那为何目前其它大规模模型不走稀疏模型的路线呢?因为 Sparse 模型存在训练不稳定、容易过拟合等问题,不太容易训练好。所以,如何修正稀疏模型面临的问题,<strong>设计出更容易训练的稀疏模型,是很重要的未来研究方向</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>重要研究拓展方向 <br />\n1、任务层 —— 增加 LLM 推理能力 <br />\n2、接口层 —— 命令更自然:怎么把 ICL 逐渐过渡成 zero-shot 的 Instruct <br />\n3、任务层 —— 多模态拓展:先在 NLP 和 Code 上奔向 AGI,把 CV、音频、AI for science、自动驾驶、机器人逐渐囊括进来 <br />\n4、模型层 —— 训练更容易:稀疏矩阵问题很多(训练不稳定、容易过拟合)的解决</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>为什么稀疏结构效果好?<br />\n1、计算快:如果很稠密,则对很多知识的提炼都耦合在了一起。如果比较稀疏,就类似人脑,对不同功能、不同知识等内容是分开存储的,耦合少、计算速度就快。<br />\n2、好训练:越稀疏训练成本越低\n3、缺点:训练不稳定,容易过拟合</p>\n</blockquote>\n\n<h2 id=\"七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</h2>\n\n<p>如果希望能复刻类似 ChatGPT 这种效果令人惊艳的 LLM 模型,综合目前的各种研究结论,在做技术选型时需要重点权衡如下问题:</p>\n\n<p>首先,在预训练模式上,我们有三种选择:GPT 这种自回归语言模型,Bert 这种双向语言模型,以及 T5 这种混合模式(Encoder-Decoder 架构,在 Encoder 采取双向语言模型,Decoder 采取自回归语言模型,所以是一种混合结构,但其本质仍属于 Bert 模式)。我们应选择 GPT 这种自回归语言模型,其原因在本文范式转换部分有做分析。目前看,<strong>国内 LLM 在做这方面技术选型的时候,貌似很多都走了 Bert 双向语言模型或 T5 混合语言模型的技术路线,很可能方向走偏了</strong>。</p>\n\n<p>第二,<strong>强大的推理能力是让用户认可 LLM 的重要心理基础</strong>,而如果希望 LLM 能够具备强大的推理能力,根据目前经验,最好在做预训练的时候,要引入大量代码和文本一起进行 LLM 训练。至于其中的道理,在本文前面相关部分有对应分析。</p>\n\n<p>第三,如果希望模型参数规模不要那么巨大,但又希望效果仍然足够好,此时有两个技术选项可做配置:要么增强高质量数据收集、挖掘、清理等方面的工作,意思是我模型参数可以是 ChatGPT / GPT 4 的一半,但是要想达到类似的效果,那么高质量训练数据的数量就需要是 ChatGPT/GPT 4 模型的一倍(Chinchilla 的路子);另外一个可以有效减小模型规模的路线是采取文本检索(Retrieval based)模型 + LLM 的路线,这样也可以在效果相当的前提下,极大减少 LLM 模型的参数规模。这两个技术选型不互斥,反而是互补的,也即是说,可以同时采取这两个技术,在模型规模相对比较小的前提下,达到超级大模型类似的效果。</p>\n\n<p>第四,超级大模型因为模型规模大,所以训练成本过高,导致很少有机构有能力去做这件事。而且由上文分析可见,继续不断推大 LLM 模型规模是肯定会发生、也应该去做的事情。于是,如何通过技术手段降低 LLM 的训练成本就很重要。LLM 的特征抽取器 Sparse 化是有效降低模型训练及推理成本的技术选择。由此可见,随着模型越来越大,LLM 模型 Sparse 化是一个应该考虑的选项。</p>\n\n<p>第五,ChatGPT 是目前最接近理想 LLM 的技术方案,而理想中的 LLM 应该是以一个几乎无所不能的基础通用大模型作为依托,来支持各种各样的上层任务类型。目前看,支持越来越多的任务类型,主要是通过增加 LLM 预训练数据的多样性来达成的,数据多样性越好,LLM 能够支持的任务类型就越丰富。所以,<strong>应该重视通过增加数据多样性来增加 LLM 新能力的思路</strong>。</p>\n\n<p>第六,易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务,而 LLM 要能够理解这些 Instruct 的真实含义。另外,也要注意这些 Instruct 是符合人类真实需求的,就是说,要从最终用户那里收集任务表述方式,而不能靠研发人员自己的臆想或猜测。ChatGPT 给我最大的启发其实是这一点,至于是否用增强学习我倒觉得不重要,其它替代技术应该也能做类似的事情。</p>\n\n<h2 id=\"八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</h2>\n\n<p>为什么是 OpenAI 作出了 ChatGPT,而不是其它机构呢?我们在这里可以做个简单分析。</p>\n\n<p>在本文开头,我们提到了 OpenAI 看待 LLM 的理念。OpenAI 是怎么看待 LLM 的呢?回顾它不断推出的技术,可以看出,它其实从 GPT 1.0 开始,基本就坚定地把 LLM 看做是通往 AGI 的一条必由之路。具体而言,在 OpenAI 眼中,未来的 AGI 应该长这个样子:有一个任务无关的超大型 LLM,用来从海量数据中学习各种知识,这个 LLM 以生成一切的方式,来解决各种各样的实际问题,而且它应该能听懂人类的命令,以便于人类使用。其实对 LLM 发展理念的理解,在前半部分,就是「构建一个任务无关的超大型 LLM,让它从海量数据中学习各种知识」,这一点几乎是大家的共识,能体现出 OpenAI 眼光的其实是后半部分。</p>\n\n<p>OpenAI 的理念比较超前,对自我定位从一开始就定得比较高,始终坚定不移地探索上述方式是否可以实现 AGI。OpenAI 之所以能作出 ChatGPT,胜在一个是定位比较高,另一个是不受外界干扰,态度上坚定不移。</p>\n\n<p>我们可以回顾下它走的一些关键路程:GPT 1.0 走的是生成模式的自回归语言模型路线,比 Bert 出来的还早些。Bert 证明了:双向语言模型对于很多 NLP 理解类任务,效果比自回归这种单向语言模型效果更好。尽管如此,GPT 2.0 并没有因此切换到双向语言模型这条路上,仍然走文本生成的路,而且开始尝试零示例(zero shot)prompt 和少量示例(few shot)prompt。其实这时候, OpenAI 心目中的 AGI 已经开始浮出水面,逐渐显示出轮廓了。只是因为 zero shot/few shot 效果比 Bert+fine-tuning 差的比较远,所以大家都没太当回事,甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候,我估计即使是 OpenAI 自己,也不一定能确保这条路肯定能走通。</p>\n\n<p>但是,这不妨碍它继续在这条路上往后走。GPT 3.0 已经展示出了比较强大的 zero shot/few shot prompt 能力,这时候 OpenAI 心目中的 AGI 已经完全漏出水面,轮廓清晰,而且它的效果也证明了这条路,是有较大可能走得通的。GPT 3.0 是一个决定 LLM 发展方向的叉路口和分水岭,与之对应的另外一条路是「Bert+fine-tuning」模式。在这个岔路口,不同的从业者选择走上了不同的道路,后面的技术差距也是从这里开始拉开的。很遗憾地是,国内很多从业者选择继续在「Bert+fine-tuning」这条路上往后走,这也是造成今天落后局面的一个关键时间节点。再往后,就是 InstructGPT 和 ChatGPT 了,OpenAI 通过 ChatGPT 证明了一点;虽然我们距离真正的 AGI,可能还有很长的路要走,但是通过超大 LLM 走向 AGI 这条路,目前看是可行的。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"AIGC":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</h2>\t\t\n\t<time datetime=\"2023-01-08T18:13:09+00:00\" class=\"by-line\">08 Jan 2023, 杭州 | 麦克船长 | 总计 40590 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-1.jpeg\" alt=\"image\" /></p>\n\n<p>原文链接:<a href=\"https://zhuanlan.zhihu.com/p/597586623\">https://zhuanlan.zhihu.com/p/597586623</a></p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一潮流之巅nlp-研究范式的转换\" id=\"markdown-toc-一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</a> <ul>\n <li><a href=\"#1范式转换-10从深度学习到两阶段预训练模型\" id=\"markdown-toc-1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</a> <ul>\n <li><a href=\"#11影响一中间任务的消亡\" id=\"markdown-toc-11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</a></li>\n <li><a href=\"#12影响二不同研究方向技术路线的统一\" id=\"markdown-toc-12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</a></li>\n </ul>\n </li>\n <li><a href=\"#2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\" id=\"markdown-toc-2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</a> <ul>\n <li><a href=\"#21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\" id=\"markdown-toc-21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</a></li>\n </ul>\n </li>\n <li><a href=\"#影响一让-llm-适配人的新型交互接口\" id=\"markdown-toc-影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</a></li>\n <li><a href=\"#影响二很多-nlp-子领域不再具备独立研究价值\" id=\"markdown-toc-影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</a></li>\n <li><a href=\"#影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\" id=\"markdown-toc-影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</a></li>\n </ul>\n </li>\n <li><a href=\"#二学习者从无尽数据到海量知识\" id=\"markdown-toc-二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</a> <ul>\n <li><a href=\"#1求知之路llm-学到了什么知识\" id=\"markdown-toc-1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</a></li>\n <li><a href=\"#2记忆之地llm-如何存取知识\" id=\"markdown-toc-2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</a></li>\n <li><a href=\"#3知识涂改液如何修正-llm-里存储的知识\" id=\"markdown-toc-3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</a></li>\n </ul>\n </li>\n <li><a href=\"#三规模效应当-llm-越来越大时会发生什么\" id=\"markdown-toc-三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</a></li>\n <li><a href=\"#四人机接口从-in-context-learning-到-instruct-理解\" id=\"markdown-toc-四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</a> <ul>\n <li><a href=\"#1神秘的-in-context-learning\" id=\"markdown-toc-1神秘的-in-context-learning\">1、神秘的 In Context Learning</a></li>\n <li><a href=\"#2神奇的-instruct-理解\" id=\"markdown-toc-2神奇的-instruct-理解\">2、神奇的 Instruct 理解</a></li>\n <li><a href=\"#3in-context-learning-和-instruct-的联系\" id=\"markdown-toc-3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</a></li>\n </ul>\n </li>\n <li><a href=\"#五智慧之光如何增强-llm-的推理能力\" id=\"markdown-toc-五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</a> <ul>\n <li><a href=\"#1基于-prompt-的方法\" id=\"markdown-toc-1基于-prompt-的方法\">1、基于 Prompt 的方法</a></li>\n <li><a href=\"#2代码预训练增强-llm-推理能力\" id=\"markdown-toc-2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</a></li>\n <li><a href=\"#3关于-llm-推理能力的思考\" id=\"markdown-toc-3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</a></li>\n </ul>\n </li>\n <li><a href=\"#六未来之路llm-研究趋势及值得研究的重点方向\" id=\"markdown-toc-六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</a> <ul>\n <li><a href=\"#探索-llm-模型的规模天花板\" id=\"markdown-toc-探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</a></li>\n <li><a href=\"#增强-llm-的复杂推理能力\" id=\"markdown-toc-增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</a></li>\n <li><a href=\"#llm-纳入-nlp-之外更多其它研究领域\" id=\"markdown-toc-llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</a></li>\n <li><a href=\"#更易用的人和-llm-的交互接口\" id=\"markdown-toc-更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</a></li>\n <li><a href=\"#建设高难度的综合任务评测数据集\" id=\"markdown-toc-建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</a></li>\n <li><a href=\"#高质量数据工程\" id=\"markdown-toc-高质量数据工程\">高质量数据工程</a></li>\n <li><a href=\"#超大-llm-模型-transformer-的稀疏化\" id=\"markdown-toc-超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</a></li>\n </ul>\n </li>\n <li><a href=\"#七取经之路复刻-chatgpt-时要注意些什么\" id=\"markdown-toc-七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</a></li>\n <li><a href=\"#八chatgpt为什么是-openai\" id=\"markdown-toc-八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</a></li>\n</ul>\n\n<p>ChatGPT 出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型(LLM,Large Language Model)效果能好成这样;惊醒是顿悟到我们对LLM的认知及发展理念,距离世界最先进的想法,差得有点远。我属于既惊喜又惊醒的那一批,也是典型的中国人,中国人善于自我反思,于是开始反思,而这篇文章正是反思的结果。</p>\n\n<p>实话实说,国内在 LLM 模型相关技术方面,此刻,距离最先进技术的差距进一步加大了。技术领先或技术差距这事情,我觉得要动态地以发展的眼光来看。<strong>在 Bert 出现之后的一到两年间,其实国内在这块的技术追赶速度还是很快的,也提出了一些很好的改进模型,差距拉开的分水岭应该是在 GPT 3.0 出来之后,也就是 2020 年年中左右</strong>。在当时,其实只有很少的人觉察到:GPT 3.0 它不仅仅是一项具体的技术,其实体现的是 LLM 应该往何处去的一个发展理念。自此之后,差距拉得越来越远,ChatGPT 只是这种发展理念差异的一个自然结果。所以,我个人认为,抛开是否有财力做超大型 LLM 这个因素,如果单从技术角度看,差距主要来自于对 LLM 的认知以及未来应往何处去的发展理念的不同。</p>\n\n<p>国内被国外技术甩得越来越远,这个是事实,不承认也不行。前阵子网上很多人担忧说国内 AI 现在处于「危急存亡之秋」,我觉得倒也不至于这么严重。君不见,这个世界上,具备这么超前眼光的只有 OpenAI 一家吗?<strong>包括 Google 在内,其实对于 LLM 发展理念的理解,明显都落后 OpenAI 一个身位。现实是 OpenAI 表现过于优秀,把所有人都甩开了,不仅仅是国内</strong>。</p>\n\n<p>我觉得,OpenAI 对 LLM 在理念及相关技术方面,领先国外的 Google、DeepMind 大约半年到一年的时间,领先国内大概两年左右的时间。在 LLM 这个事情上,感觉梯队很明显,Google 应该是排在第二位,最能体现 Google 技术眼光的是 PaLM 和 Pathways,推出时间大概在 22 年 2 月到 4 月间,同一时期,OpenAI 推出的却是 InstructGPT,从这里就可以看出 Google 和 OpenAI 的差距了,至于为何这么说,你看了我后面的正文后大概能理解。DeepMind 之前的重心一直在强化学习攻克游戏和 AI for science 这些方面,切入LLM 其实很晚,应该是21 年才开始重视这个方向,目前也处于追赶状态。Meta 就更不用说了,重心一直不在 LLM 上,目前感觉也发力开始追赶。这还是目前做得最好的一批机构,尚且如此,更何况国内呢?我觉得情有可原。至于 OpenAI 关于 LLM 的理念是什么,我在本文的最后一部分,会谈谈我的认知。</p>\n\n<p>本文梳理自 GPT 3.0 出现之后的主流 LLM 技术,能够让您对 LLM 领域的技术脉络,LLM 技术发展过程中出现过的不同发展理念,乃至未来可能的发展趋势,有比较清晰的认知。当然,很多地方讲的内容是我个人看法,有很大的主观性,错漏难免,所以还请谨慎参考。</p>\n\n<p>本文试图回答下面一些问题:ChatGPT 是否带来了 NLP 乃至 AI 领域的研究范式转换?如果是,那会带来怎样的影响?LLM 从海量数据中学到了什么知识?LLM 又是如何存取这些知识的?随着LLM规模逐步增大,会带来什么影响?什么是 In Context Learning?为什么它是一项很神秘的技术?它和 Instruct 又是什么关系?LLM 具备推理能力吗?思维链 CoT 又是怎么做的?等等,相信看完,能让您对这些问题有一个答案。</p>\n\n<p>首先,在谈 LLM 技术现状前,先宏观地谈下我心目中的研究范式转换问题。这样,我们才能「先见森林,再见树木」,对具体技术为何会是如此变化有个更清晰的认知。</p>\n\n<h2 id=\"一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</h2>\n\n<p>如果我们把时间线往前拉得更长一些,回到 NLP 领域的深度学习时代,在更长时间窗口内观察技术变迁及其影响,可能会更容易看清其中的一些关键节点。我个人认为,在最近 10 年来NLP领域的技术发展过程中,可能存在两次大的研究范型转换。</p>\n\n<h3 id=\"1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在深度学习引入 NLP 领域(2013 年左右),到 GPT 3.0 出现之前(2020 年 5 月左右)。</p>\n\n<p>在 Bert 和 GPT 模型出现之前,NLP 领域流行的技术是深度学习模型,而 NLP 领域的深度学习,主要依托于以下几项关键技术:以大量的改进 LSTM 模型及少量的改进 CNN 模型作为典型的特征抽取器;以 Sequence to Sequence(或叫 encoder-decoder 亦可)+ Attention 作为各种具体任务典型的总体技术框架。</p>\n\n<p>在这些核心技术加持下,NLP 领域深度学习的主要研究目标,如果归纳一下,是如何有效增加模型层深或模型参数容量。就是说,怎么才能往 encoder 和 decoder 里不断叠加更深的 LSTM 或 CNN 层,来达成增加层深和模型容量的目标。这种努力,尽管确实不断增加了模型层深,但是从解决具体任务的效果角度看,总体而言,不算很成功,或者说和非深度学习方法相比,带来的优势不算大。</p>\n\n<p>深度学习之所以不够成功,我认为主要原因来自于两个方面:一方面是某个具体任务有限的训练数据总量。随着模型容量的增加,需要靠更大量的训练数据来支撑,否则即使你能把深度做起来,任务效果也做不上去。而在预训练模型出现之前,很明显这是 NLP 研究领域一个严重问题;另外一个方面是 LSTM/CNN 特征抽取器,表达能力不够强。意思是就算给你再多的数据也没用,因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因,阻碍了深度学习在 NLP 领域的成功突围。</p>\n\n<p>Bert / GPT 这两个预训练模型的出现,无论在学术研究角度看,还是工业应用角度来看,都代表了 NLP 领域的一个技术飞跃,并带来了整个领域研究范式的转换。这种范式转换带来的影响,体现在两个方面:首先,是部分 NLP 研究子领域的衰退乃至逐步消亡;其次,NLP 不同子领域的技术方法和技术框架日趋统一,在 Bert 出现后一年左右,技术栈基本收敛到两种技术模式中。关于这两点,我们分头来谈。</p>\n\n<h4 id=\"11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</h4>\n\n<p>NLP 是一个宏观研究领域的统称,里面有五花八门具体的子领域与子方向,如果仔细分析,从任务的性质角度,可以把这些任务分成两大类:一类可以叫做「中间任务」,一类可以称为「最终任务」。</p>\n\n<p>典型的中间任务包括:中文分词、词性标注、NER、句法分析、指代消解、语义 Parser 等,这类任务一般并不解决应用中的实际需求,大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的,比如几乎没有需求说,我要一个句法 Parser,把这个句子的句法分析树给用户看看,用户不需要看到这些NLP的中间阶段处理结果,他只关心某个具体任务你有没有干好。「最终任务」包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等,有很多。这类任务的特点是每个子领域都解决某个实际需求,任务结果基本能直接呈现给用户,比如用户确实存在给你一句英文,告诉他中文是什么的需求。</p>\n\n<p>按理说,「中间任务」就不应该出现,而之所以会存在,这是 NLP 技术发展水平不够高的一种体现。在技术发展早期阶段,因为当时的技术相对落后,很难一步做好有难度的最终任务。比如机器翻译,早期技术要做好机器翻译是很困难的,于是科研人员就把难题分而治之,分解成分词、词性标注、句法分析等各种中间阶段,先把每个中间阶段做好,然后再拼起来完成最终任务,这也是没办法的事情。</p>\n\n<p>但是自从 Bert/GPT 出现之后,其实就没有必要做这些中间任务了,因为通过大量数据的预训练,Bert/GPT 已经把这些中间任务作为语言学特征,吸收到了 Transformer 的参数里,此时我们完全可以端到端地直接解决那些最终任务,而无须对这种中间过程专门建模。这里可能争议最大的是中文分词,其实道理也是一样的,哪些字应该组成一个词,这个其实你不用管,让 LLM 自己当特征去学就行了,只要对于解决任务有帮助,它自然会去学该学的合理分词方式,也未必一定要和我们人类理解的分词规则相同。</p>\n\n<p>基于以上认知,其实在Bert/GPT一出现,你就应该得出这类NLP的中间阶段的任务,会逐步退出历史舞台这个结论。</p>\n\n<h4 id=\"12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</h4>\n\n<p>在说明具体影响前,我们先讨论下另外一种 NLP 任务划分方式,这对于理解后面内容有帮助。如果对「最终任务」进一步进行分类,又大致可以分为两大不同类型的任务:自然语言理解类任务和自然语言生成类任务。如果排除掉「中间任务」的话,典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等,这种任务本质上都是分类任务,就是说输入一个句子(文章),或者两个句子,模型参考所有输入内容,最后给出属于哪个类别的判断。自然语言生成也包含很多 NLP 研究子方向,比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本,对应地,模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上。</p>\n\n<p>自从 Bert/GPT 模型诞生后,出现了明显的技术统一趋向。首先,NLP 中不同的子领域,其特征抽取器都逐渐从 LSTM/CNN 统一到 Transformer 上。其实,自Bert公开后不久,就应该意识到,这必然会成为技术趋势。而且,目前 Transformer 不仅统一了 NLP 诸多领域,也正在逐步地替换图像处理各种任务中被广泛使用的 CNN 等其它模型的进程之中,类似的,多模态模型目前也基本都采用了 Transformer 模型。这种Transformer从NLP出发,攻城略地逐步统一AI越来越多领域的趋势,起始于 2020 年底出现的 Vision Transformer (ViT) ,之后蓬勃发展,到目前已大获成功,且其继续向更多领域拓展的势头会越来越迅猛。</p>\n\n<p>其次,大多数 NLP 子领域的研发模式切换到了两阶段模式:模型预训练阶段 + 应用微调(Fine-tuning)或应用 Zero/Few Shot Prompt 模式。更准确地说,NLP 各种任务其实收敛到了两个不同的预训练模型框架里:对于自然语言理解类任务,其技术体系统一到了以 Bert 为代表的「双向语言模型预训练 + 应用 Fine-tuning」模式;而对于自然语言生成类任务,其技术体系则统一到了以GPT 2.0 为代表的「自回归语言模型(即从左到右单向语言模型)+ Zero/Few Shot Prompt」模式。至于为何会分化成两条技术路线,有其必然性,关于这点我们放在后面解释。</p>\n\n<p>这两种模式,看似比较相像,但其背后蕴含了迥异的发展思路,也会导向不同的未来发展方向。不过遗憾的是,我们中的绝大多数人,在当时都低估了GPT 这条发展路线的潜力,而把视觉中心聚焦到了Bert这种模式上。</p>\n\n<h3 id=\"2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在 GPT 3.0 出现之后(20 年 6 月左右),一直到目前为止,我们应该正处于这个范式转换过程中。</p>\n\n<p>ChatGPT 是触发这次范型转换的关键节点,但是在 InstructGPT 出现之前,其实 LLM 处于这次范式转换前的一个过渡期。</p>\n\n<h4 id=\"21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</h4>\n\n<p>前面说过,在预训练模型发展的早期,技术框架收敛到了 Bert 模式和 GPT 模式这两种不同的技术范型,而且人们普遍更看好 Bert 模式一些,相当多数的后续技术改进,都是沿着 Bert 那条路走的。但是,随着技术的继续发展,你会发现,目前规模最大的 LLM 模型,几乎清一色都是类似 GPT 3.0 这种「自回归语言模型 + Prompting」模式的,比如 GPT-3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA 等,没有例外。为什么会这样呢?背后一定有其必然性,我认为可能主要源于两个原因。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-2.jpeg\" alt=\"image\" /></p>\n\n<p>首先,Google 的 T5 模型,在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示,标为红色的是个文本分类问题,黄色的是判断句子相似性的回归或分类问题,这都是典型的自然语言理解问题。在 T5 模型里,这些自然语言理解问题在输入输出形式上和生成问题保持了一致,也就是说,可以把分类问题转换成让 LLM 模型生成对应类别的字符串,这样理解和生成任务在表现形式就实现了完全的统一。</p>\n\n<p>这说明自然语言生成任务,在表现形式上可以兼容自然语言理解任务,若反过来,则很难做到这一点。这样的好处是:同一个 LLM 生成模型,可以解决几乎所有 NLP 问题。而如果仍然采取 Bert 模式,则这个 LLM 模型无法很好处理生成任务。既然这样,我们当然倾向于使用生成模型,这是一个原因。</p>\n\n<p>第二个原因,如果想要以零示例提示语(zero shot prompting)或少数示例提示语(few shot prompting)的方式做好任务,则必须要采取 GPT 模式。现在已有研究(参考:<a href=\"https://arxiv.org/pdf/2205.11726\">《On the Role of Bidirectionality in Language Model Pre-Training》</a>)证明:如果是以 fine-tuning 方式解决下游任务,Bert模式的效果优于 GPT 模式;若是以 zero shot / few shot prompting 这种模式解决下游任务,则GPT模式效果要优于 Bert 模式。这说明了,生成模型更容易做好 zero shot/few shot prompting 方式的任务,而Bert模式以这种方式做任务,是天然有劣势的。这是第二个原因。</p>\n\n<p>但是问题来了:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?要解释清楚这个问题,我们首先需要搞清楚另外一个问题:什么样的 LLM 模型,对我们是最理想的?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-3.jpeg\" alt=\"image\" /></p>\n\n<p>上图展示了一个理想的 LLM 该有的样子。首先,LLM 应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它,它应该能够自动从中学习到里面包含的所有知识点,学习过程不需要人的介入,并且能灵活应用所学知识,来解决实际问题。因为数据是海量的,要吸收所有知识,就要非常多的模型参数来存储知识,所以这个模型必然会是一个巨无霸模型。</p>\n\n<p>其次,LLM 应该能解决 NLP 任何子领域的问题,而不仅支持有限领域,甚至它应该可以响应 NLP 之外其它领域的问题,最好是任意领域的问题都能得到很好地回答。</p>\n\n<p>再者,当我们使用 LLM 解决某个具体领域问题的时候,应该用我们人类习惯的表达方式,就是说LLM应该理解人类的命令。这体现出让 LLM 适配人,而不是反过来,让人去适配 LLM 模型。人适配 LLM 的典型例子,比如绞尽脑汁去尝试各种不同的 prompt,以试图找到好的提示语,才能很好地解决手头问题。关于这点,上图在人类和 LLM 交互的接口层,举了几个例子,说明什么是好的人使用 LLM 模型的接口形式。</p>\n\n<p>看完这个理想中的 LLM,我们再回头解释上面遗留的问题:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?有两个原因。</p>\n\n<p>第一,这个 LLM 模型规模必然非常巨大,有能力作出这个模型,或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人,就算你把模型开源出来,他们也无力部署这个模型,更不用说再用 Fine-tuning 这种模式去修改模型参数了。所以,我们应该追求不修正模型参数,就能让任务需求方完成任务的方式,也就是应该采取 prompt 模式完成任务,而非 Fine-tuning 模式(由此可看出,soft prompting 技术方向是违背这个发展趋势的)。模型制作方则将 LLM 作成公用服务,以 LLM as Service 的模式运行。作为服务支持方,考虑到千变万化的用户需求,所以 LLM 模型制作方更要追求让 LLM 能完成尽可能多类型的任务,这是附带的影响,也是为何超级大模型一定会追求走向AGI的现实因素。</p>\n\n<p>第二,zero shot prompting 也好,few shot prompting 也好,甚至促进LLM推理能力的思维链(CoT,Chain of Thought)Prompting 也好,就是上图中接口层中的现有技术。具体而言,zero shot prompting 的初衷,其实就是人类和 LLM 的理想接口,直接用人类所习惯的任务表述方式让 LLM 做事情,但是发现 LLM 并不能很好地理解,效果也不好。经过继续研究,转而发现:对于某项任务,如果给 LLM 几个示例,用这些示例来代表任务描述,效果会比 zero shot prompting 好,于是大家都去研究更好的 few shot prompting 技术。可以理解为,本来我们希望 LLM 能够用人类常用的命令方式来执行某个任务,但是目前技术还做不到,所以退而求其次,用这些替代技术来表达人类的任务需求。</p>\n\n<p>如果理解了上述逻辑,很容易得出如下结论:few shot prompting(也被称为In Context Learning)只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务,而且 LLM 可以理解,那么,我们肯定会毫不犹豫地抛弃这些过渡期的技术,原因很明显,用这些方法来描述任务需求,并不符合人类的使用习惯。</p>\n\n<p>这也是为何我将 GPT 3.0 + Prompting 列为过渡期技术的原因,ChatGPT 的出现,改变了这个现状,用 Instruct 取代了 Prompting,由此带来新的技术范式转换,并产生若干后续影响。</p>\n\n<h3 id=\"影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</h3>\n\n<p>在理想 LLM 的背景下,我们再来看 ChatGPT,能更好理解它的技术贡献。ChatGPT 应该是目前所有的现有技术里,最接近理想 LLM 的技术方法。如果归纳下 ChatGPT 最突出特点的话,我会用下面八个字「能力强大,善解人意」。</p>\n\n<p>「能力强大」这一点,我相信应该主要归功于 ChatGPT 所依托的基础 LLM GPT-3.5。因为 ChatGPT 尽管加入了人工标注数据,但是量级只有数万,这个规模的数据量,和训练 GPT 3.5 模型使用的几千亿 token 级别的数据量相比,包含的世界知识(数据中包含的事实与常识)可谓沧海一粟,几可忽略,基本不会对增强 GPT 3.5 的基础能力发挥什么作用。所以它的强大功能,应该主要来自于隐藏在背后的 GPT 3.5。GPT 3.5 对标理想 LLM 模型中的那个巨无霸模型。</p>\n\n<p>那么,ChatGPT 向 GPT 3.5 模型注入新知识了吗?应该是注入了,这些知识就包含在几万人工标注数据里,不过注入的不是世界知识,而是人类偏好知识。所谓「人类偏好」,包含几方面的含义:首先,是人类表达一个任务的习惯说法。比如,人习惯说「把下面句子从中文翻译成英文」,以此表达一个「机器翻译」的需求,但是 LLM 又不是人,它怎么会理解这句话到底是什么意思呢?你得想办法让 LLM 理解这句命令的含义,并正确执行。所以,ChatGPT 通过人工标注数据,向GPT 3.5 注入了这类知识,方便 LLM 理解人的命令,这是它“善解人意”的关键。其次,对于什么是好的回答,什么是不好的回答,人类有自己的标准,例如比较详细的回答是好的,带有歧视内容的回答是不好的,诸如此类。这是人类自身对回答质量好坏的偏好。人通过 Reward Model 反馈给 LLM 的数据里,包含这类信息。总体而言,ChatGPT 把人类偏好知识注入 GPT 3.5,以此来获得一个听得懂人话、也比较礼貌的 LLM。</p>\n\n<p>可以看出,ChatGPT 的最大贡献在于:基本实现了理想 LLM 的接口层,让 LLM 适配人的习惯命令表达方式,而不是反过来让人去适配 LLM,绞尽脑汁地想出一个能 Work 的命令(这就是 instruct 技术出来之前,prompt 技术在做的事情),而这增加了 LLM 的易用性和用户体验。是 InstructGPT / ChatGPT 首先意识到这个问题,并给出了很好的解决方案,这也是它最大的技术贡献。相对之前的 few shot prompting,它是一种更符合人类表达习惯的人和 LLM 进行交互的人机接口技术。</p>\n\n<p>而这必将启发后续的 LLM 模型,继续在易用人机接口方面做进一步的工作,让 LLM 更听话。</p>\n\n<h3 id=\"影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</h3>\n\n<p>就 NLP 领域而言,这次范式转换,意味着很多目前独立存在的 NLP 研究领域,将被纳入 LLM 的技术体系,进而不再独立存在,逐步消失。经过第一次范式转换,尽管 NLP 中很多「中间任务」,继续作为独立研究领域存在不再必要,但是大多数「最终任务」,仍然是以独立研究领域存在的,只是切换成在「预训练 + fine-tuning」框架下,面对领域独有问题,陆续提出新的改进方案。</p>\n\n<p>目前研究表明,很多 NLP 任务,随着 LLM 模型规模增长,效果会大幅提升。据此,我觉得可得到如下推论:大多数某领域所谓「独有」的问题,大概率只是缺乏领域知识导致的一种外在表象,只要领域知识足够多,这个所谓领域独有的问题,就可以被很好地解决掉,其实并不需要专门针对某个具体领域问题,冥思苦想去提出专用解决方案。也许 AGI 的真相超乎意料地简单:你只要把这个领域更多的数据交给 LLM,让它自己学习更多知识即可。</p>\n\n<p>在这个背景下,同时,ChatGPT 证明了我们现在是可以直接去追求理想 LLM 模型的,那么,未来的技术发展趋势应该是:追求规模越来越大的 LLM 模型,通过增加预训练数据的多样性,来涵盖越来越多的领域,LLM 自主从领域数据中通过预训练过程学习领域知识,随着模型规模不断增大,很多问题随之得到解决。研究重心会投入到如何构建这个理想 LLM 模型,而非去解决某个领域的具体问题。这样,越来越多 NLP 的子领域会被纳入 LLM 的技术体系,进而逐步消失。</p>\n\n<p>我认为,判断某个具体领域是否该立即停止独立研究,其判断标准可采取以下两种方法,占其一即可:第一,判断某个任务,是否 LLM 的研究效果超过人类表现,对于那些 LLM 效果超过人类的研究领域,已无独立研究的必要。举个例子,GLUE 与 SuperGLUE 测试集合里的很多任务,目前 LLM 效果已超过人类表现,与这个数据集合密切相关的研究领域,其实就没有继续独立存在的必要。第二,对比两种模式的任务效果,第一种模式是用较大的领域专用数据进行 Fine-tuning,第二种是 few-shot prompting 或 instruct-based 方法。如果第二种方法效果达到或超过第一种方法,则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看,其实很多研究领域,目前 fine-tuning 效果还是占优的(因为这种模式领域训练数据量大),看似还可独立存在。但是考虑到很多任务随着模型规模增大,few shot prompting 效果持续增长,随着更大模型的出现,这个拐点很可能短期就会达到。</p>\n\n<p>如果上述猜测成立,将意味着如下残酷事实:对于很多 NLP 领域的研究人员,将面临往何处去的选择,是继续做领域独有问题呢?还是放弃这种看似前途不大的方式,转而去建设更好的LLM?如果选择转向去建设 LLM,又有哪些机构有能力、有条件去做这个事情呢?你对这个问题的回答会是什么呢?</p>\n\n<h3 id=\"影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</h3>\n\n<p>如果站在 AGI 的视角,参照之前描述的理想 LLM 模型,它所能完成的任务,不应局限于 NLP 领域,或某一两个学科领域,理想中的 LLM 应该是领域无关的通用人工智能模型,它现在在某一两个领域做得好,不代表只能做这些任务。ChatGPT 的出现,证明了现在这个时期,我们去追求AGI是有可行性的,而现在是抛开「领域学科」这个思维束缚的时候了。</p>\n\n<p>ChatGPT 除了展示出以流畅的对话形式解决各种 NLP 任务外,也具备强大的代码能力。很自然的,之后越来越多其它的研究领域,也会被逐步纳入 LLM 体系中,成为通用人工智能的一部分。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-4.jpeg\" alt=\"image\" /></p>\n\n<p>LLM 从 NLP 向外进行领域拓展,一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入,让LLM成为一个支持多模态输入输出的通用人机接口,典型的例子包括 DeepMind 的 Flamingo 和微软的<a href=\"https://arxiv.org/pdf/2206.06336.pdf\">《Language Models are General-Purpose Interfaces》</a>,上图展示了这种方式的概念结构。</p>\n\n<p>我的判断是无论是图像还是多模态,未来被融入 LLM 成为好用的功能,可能比我们想象的进度要慢。主要原因在于:尽管图像领域最近两年也一直在模仿 Bert 预训练的路子,尝试引入自监督学习,释放模型自主从图像数据中学习知识的能力,典型技术就是“对比学习”和 MAE,这是两条不同的技术路线。然而,从目前效果来看,尽管取得了很大的技术进步,但貌似这条路尚未走通,这体现在图像领域预训练模型应用到下游任务,带来的效果收益,远不如 Bert 或 GPT 应用在 NLP 下游任务那样显著。所以,图像预处理模型仍需深入探索,以释放图像数据的潜力,而这会迟滞它们被统一到 LLM 大模型的时间。当然,如果哪天这条路被趟通,大概率会复现NLP领域目前的局面,就是图像处理各个研究子领域可能会逐步消失,被融入到大型 LLM 中来,直接完成终端任务。</p>\n\n<p>除了图像与多模态,很明显,其它领域也会逐渐被纳入到理想 LLM 中来,这个方向方兴未艾,是具备高价值的研究主题。</p>\n\n<p>以上是我对范式转换的个人思考,接下来,我们来梳理下 GPT 3.0 之后 LLM 模型的主流技术进展。如理想 LLM 模型所示,相关的技术其实可以分为两大类;一类是关于 LLM 模型如何从数据中吸收知识,也包括模型规模增长对 LLM 吸收知识能力带来的影响;第二类是关于人如何使用 LLM 内在能力来解决任务的人机接口,包括In Context Learning 和 Instruct 两种模式。思维链(CoT)prompting 这种 LLM 推理技术,本质上也属于 In Context Learning,因为比较重要,我就把它们单独拎出来讲一下。</p>\n\n<h2 id=\"二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</h2>\n\n<p>从目前研究结果看,Transformer 是足够强大的特征抽取器,尚不需要做特别的改进。那么通过预训练过程,Transformer 学到了什么?知识是如何存取的?我们又如何修正错误知识?本节讲述这方面的研究进展。</p>\n\n<h3 id=\"1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</h3>\n\n<p>LLM 从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,可以分为语言类知识和世界知识两大类。</p>\n\n<p>语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于 LLM 能否捕获语言知识有较长研究历史,自从 Bert 出现以来就不断有相关研究,很早就有结论,各种实验充分证明 LLM 可以学习各种层次类型的语言学知识,这也是为何使用预训练模型后,各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外,各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在 Transformer 的低层和中层,而抽象的语言知识比如语义类知识,广泛分布在 Transformer 的中层和高层结构中。</p>\n\n<p>世界知识指的是在这个世界上发生的一些真实事件(事实型知识,Factual Knowledge),以及一些常识性知识(Common Sense Knowledge)。比如「拜登是现任美国总统」、「拜登是美国人」、「乌克兰总统泽连斯基与美国总统拜登举行会晤」,这些都是和拜登相关的事实类知识;而「人有两只眼睛」、「太阳从东方升起」这些属于常识性知识。关于 LLM 模型能否学习世界知识的研究也有很多,结论也比较一致:LLM 确实从训练数据中吸收了大量世界知识,而这类知识主要分布在 Transformer 的中层和高层,尤其聚集在中层。而且,随着 Transformer 模型层深增加,能够学习到的知识数量逐渐以指数级增加(可参考<a href=\"https://arxiv.org/pdf/2106.02902.pdf\">《BERTnesia: Investigating the capture and forgetting of knowledge in BERT》</a>)。其实,你把 LLM 看作是一种以模型参数体现的隐式知识图谱,如果这么理解,我认为是一点问题也没有的。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2011.04946\">《When Do You Need Billions of Words of Pre-training Data?》</a>这篇文章研究了预训练模型学习到的知识量与训练数据量的关系,它的结论是:对于 Bert 类型的语言模型来说,只用 1000 万到 1 亿单词的语料,就能学好句法语义等语言学知识,但是要学习事实类知识,则要更多的训练数据。这个结论其实也是在意料中的,毕竟语言学知识相对有限且静态,而事实类知识则数量巨大,且处于不断变化过程中。而目前研究证明了随着增加训练数据量,预训练模型在各种下游任务中效果越好,这说明了从增量的训练数据中学到的更主要是世界知识。</p>\n\n<h3 id=\"2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</h3>\n\n<p>由上可知,LLM 确实从数据中学到了很多语言类及世界知识。那么,对于某条具体的知识,LLM 把它存储到了哪里?又是如何提取出来的?这也是一个有意思的问题。</p>\n\n<p>显然,知识一定存储在 Transformer 的模型参数里。从 Transformer 的结构看,模型参数由两部分构成:<strong>多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中</strong>。MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点,那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-5.jpeg\" alt=\"image\" /></p>\n\n<p>但这样的定位,粒度还是太粗,无法很好回答具体某条知识是如何存储与提取的,比如「中国的首都是北京」这条知识,以三元组表达就是<北京,is-capital-of,中国>,其中「is-capital-of」代表实体间关系。<strong>这条知识它存储在 LLM 的哪里呢?</strong></p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:这些知识存哪了?我们现在有以下几点认知:<br />\n1、多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中。<br />\n2、MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点。<br />\n3、那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。<br />\n4、一些研究达成共识:Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,比如《Transformer Feed-Forward Layers Are Key-Value Memories》</p>\n</blockquote>\n\n<p><a href=\"https://arxiv.org/pdf/2012.14913.pdf\">《Transformer Feed-Forward Layers Are Key-Value Memories》</a>给出了一个比较新颖的观察视角,它把 Transformer 的 FFN 看成存储大量具体知识的 Key-Value 存储器。如上图所示(图左是原始论文图,其实不太好理解,可以看做了注释的图右,更好理解些),FFN 的第一层是个 MLP 宽隐层,这是 Key 层;第二层是 MLP 窄隐层,是 Value 层。FFN 的输入层其实是某个单词对应的 MHA 的输出结果Embedding,也就是通过 Self Attention,将整个句子有关的输入上下文集成到一起的 Embedding,代表了整个输入句子的整体信息。</p>\n\n<p>Key 层的每个神经元节点,记载了一对信息。比如对于上图中 FFN 第一个隐层的第 i 个节点 ki,也许就是它记载了 <北京,is-capital-of,中国> 这条知识。ki 节点对应的 key 向量,其实指的是节点 ki 和输入层每个节点的权重向量;而对应的 Value 向量,指的是节点 ki 和 FFN 第二层的 Value 层每个节点形成连接的权重向量。每个神经元的 Key 向量,用于识别输入中的某种语言或者知识模式,是一种模式探测器。如果输入中包含它要检测的某种模式,那么输入向量和 ki 节点的 key 权重进行向量内积计算,加上 Relu,形成 ki 的大数值响应,意味着 ki 检测到了这个模式,于是再把这个响应值,通过 ki 节点的 Value 权重向量向 FFN 第二层传播。这等价于将 Value 向量的值,用响应值加权,然后传递并体现到第二层 Value 层每个节点的输出上。如此这般,FFN 的正向传播计算过程,看起来就像是通过 Key 检测到某种知识模式,然后取出对应的 Value,并把 Value 体现在FFN的第二层输出上。当然,FFN 第二层每个节点,会收集 FFN 的 Key 层所有节点信息,所以是一种混合响应,而 Value 层所有节点的混合响应,可以解读为代表输出单词的概率分布信息。</p>\n\n<p>听着可能还是比较复杂,我们用个极端的例子来说明。我们假设上图的节点 ki就是记载 <北京,is-capital-of,中国>这条知识的 Key-Value 存储器,它的 Key 向量,用于检测「中国的首都是…」这个知识模式,它的 Value 向量,基本存储了与单词「北京」的 Embedding 比较接近的向量。当 Transformer 的输入是「中国的首都是[Mask]」的时候,ki 节点从输入层探测到这个知识模式,所以产生较大的响应输出。我们假设 Key 层其它神经元对这个输入都没有任何响应,那么对应的Value层的节点,其实只会接收到「北京」这个 Value 对应的单词 embedding,并通过 ki的大响应值,进行了进一步的数值放大。于是,Mask 位置对应的输出,就自然会输出「北京」这个单词。基本就是这么个过程,看着很复杂,其实很简单。</p>\n\n<p>而且这篇文章还指出,Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,就是说低层FFN存储词法、句法等表层知识,中层和高层存储语义及事实概念知识,这和其它研究结论是一致的。</p>\n\n<p><strong>要我猜,把 FFN 看成 Key-Value 存储器这种思路,很可能不是最终的正确答案,但是距离最终正确答案的距离,估计也不太远</strong>。</p>\n\n<h3 id=\"3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</h3>\n\n<p>既然我们已知具体的某条世界知识存储在某个或者某些 FFN 节点的参数里,自然会引发另外一个问题:我们能否修正 LLM 模型里存储的错误或者过时的知识呢?比如对于问题「英国的现任首相是谁?」鉴于近年来英国首相频繁更迭,你猜 LLM 更倾向输出「鲍里斯」还是更青睐「苏纳克」?很明显训练数据中包含“鲍里斯”的数据会更多,这种情况很大可能 LLM 会给出错误回答,于是我们就有修正 LLM 里存储的过时知识的必要性。</p>\n\n<p>如果归纳下,目前有三类不同方法来修正 LLM 里蕴含的知识:</p>\n\n<p>第一类方法从训练数据的源头来修正知识。<a href=\"https://arxiv.org/pdf/2205.11482.pdf\">《Towards Tracing Factual Knowledge in Language Models Back to the Training Data》</a>这篇文章的研究目标是:对于指定的某条知识,我们是否可以定位到是哪些训练数据导致 LLM 学会了这条知识?答案是肯定的,这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术,假设我们想要删除某条知识,则可首先定位到其对应的数据源头,删除数据源,然后重新预训练整个 LLM 模型,这样即可达成删除 LLM 中相关知识的目的。但是这里有个问题,如果修正一小部分知识,我们就需要重新做一次模型预训练,这样做明显成本太高。所以这种方法不会太有发展前景,可能比较适合那种对于某个特定类别数据的一次性大规模删除场合,不适合少量多次的常规知识修正场景,比如可能比较适合用来做去除偏见等去 toxic 内容的处理。</p>\n\n<p>第二类方法是对 LLM 模型做一次 fine-tuning 来修正知识。一个直观能想到的方法是:我们可以根据要修正成的新知识来构建训练数据,然后让 LLM 模型在这个训练数据上做 fine-tuning,这样指导 LLM 记住新的知识,遗忘旧的知识。这个方法简单直观,但是也有一些问题,首先它会带来灾难遗忘问题,就是说除了忘掉该忘的知识,还忘掉了不该忘的知识,导致这么做了之后有些下游任务效果下降。另外,因为目前的 LLM 模型规模非常大,即使是做 fine-tuning,如果次数频繁,其实成本也相当高。对这种方法感兴趣的可以参考<a href=\"https://arxiv.org/pdf/2012.00363.pdf\">《Modifying Memories in Transformer Models》</a>。</p>\n\n<p>另外一类方法直接修改 LLM 里某些知识对应的模型参数来修正知识。假设我们想要把旧知识 <英国,现任首相,鲍里斯>,修正到 <英国,现任首相,苏纳克>。首先我们想办法在 LLM 模型参数中,定位到存储旧知识的 FFN 节点,然后可以强行调整更改 FFN 中对应的模型参数,将旧知识替换成新的知识。可以看出,这种方法涉及到两项关键技术:首先是如何在 LLM 参数空间中定位某条知识的具体存储位置;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。理解这个修正 LLM 知识的过程,其实对于更深入理解 LLM 的内部运作机制是很有帮助的。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:如何修改已存储的知识?<br />\n首先是如何定位存哪了;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。</p>\n</blockquote>\n\n<h2 id=\"三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</h2>\n\n<p>我们知道,近年来,LLM 模型规模在快速增长,目前效果最好的 LLM 模型,其参数规模大都超过了千亿(100B)参数规模。比如,OpenAI 的 GPT 3 的规模为 175B,Google 的 LaMDA 规模为 137B,PaLM 的规模为 540B,DeepMind 的 Gogher 规模为 280B 等,不一而足。国内也有中文巨型模型,比如智源 GLM 规模 130B,华为「盘古」规模 200B,百度「文心」规模 260B,浪潮「源1.0」规模 245B。那么,一个很自然的问题就是:随着 LLM 模型规模不断增长,会发生些什么呢?</p>\n\n<p>预训练模型的应用往往是两阶段的:预训练阶段,及具体场景应用阶段。在预训练阶段,其优化目标是交叉熵,对 GPT 这种自回归语言模型来说,也就是看 LLM 是否正确预测到了下一个单词;而场景应用阶段,一般要看具体场景的评价指标。一般我们的直觉是:如果 LLM 模型在预训练阶段的指标越好,自然它解决下游任务的能力就越强。然而,事实并非完全如此。现有研究已证明,预训练阶段的优化指标确实和下游任务表现出正相关关系,但是并非完全正相关。也就是说,只看预训练阶段的指标,来判断一个 LLM 模型是否够好,这是不够的。基于此,我们分头来看在这两个不同阶段,随着 LLM 模型增大,有什么影响。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-6.jpeg\" alt=\"image\" /></p>\n\n<p>首先,我们先看在预训练阶段,随着模型规模逐步增大,会发生什么。OpenAI 在<a href=\"https://arxiv.org/pdf/2001.08361\">《Scaling Laws for Neural Language Models》</a>中专门研究了这个问题,并提出 LLM 模型所遵循的「伸缩法则(scaling law)」。如上图所示,这个研究证明:<strong>当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好</strong>。</p>\n\n<p>既然三个因素都重要,那么我们在实际做预训练的时候,就有一个算力如何分配的决策问题:假设用于训练 LLM 的算力总预算(比如多少 GPU 小时或者 GPU 天)给定,那么是应该多增加数据量、减少模型参数呢?还是说数据量和模型规模同时增加,减少训练步数呢?此消彼长,某个要素规模增长,就要降低其它因素的规模,以维持总算力不变,所以这里有各种可能的算力分配方案。最终 OpenAI 选择了同时增加训练数据量和模型参数,但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了:对于训练数据量和模型参数这两个要素,如果只单独增加其中某一个,这不是最好的选择,最好能按照一定比例同时增加两者,它的结论是优先增加模型参数,然后才是训练数据量。假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 5.5 倍的模型参数量,1.8 倍的训练数据量,此时模型效果最佳。</p>\n\n<p>DeepMind 的一项研究(参考<a href=\"https://arxiv.org/pdf/2203.15556\">《Training Compute-Optimal Large Language Models》</a>)更深入地探究了这个问题,其基本结论和 OpenAI 的结论差不多,比如确实需要同时增加训练数据量和模型参数,模型效果才会更好。而很多大模型在做预训练的时候,并没有考虑这一点,很多 LLM 大模型只是单调增加模型参数,而固定住了训练数据量,这个做法其实是不对的,限制了 LLM 模型的潜力。但是它修正了两者的比例关系,<strong>认为训练数据量和模型参数是同等重要的,也就是说,假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 3.3 倍的模型参数量,3.3 倍的训练数据量,这样模型效果才最好</strong>。</p>\n\n<p>这意味着:增加训练数据量的重要性,比我们之前所认为的,还要重要。基于这个认知,DeepMind 在设计 Chinchilla 模型时,在算力分配上选择了另外一种配置:对标数据量 300B、模型参数量 280B 的 Gopher 模型,Chinchilla 选择增加 4 倍的训练数据,但是将模型参数降低为 Gopher 的四分之一,大约为70B。但是无论预训练指标,还是很多下游任务指标,Chinchilla 效果都要优于规模更大的 Gopher。</p>\n\n<p>这带给我们如下启示:我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。缩小模型规模有很多好处,比如在应用的时候,推理速度会快很多等,无疑这是一个很有前途的 LLM 发展路线。</p>\n\n<p>以上是从预训练阶段来看模型规模的影响,如果从 LLM 解决下游具体任务效果的角度来看,随着模型规模增大,不同类型的任务有不同的表现,具体而言,有以下三类情况。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-7.jpeg\" alt=\"image\" /></p>\n\n<p>第一类任务完美体现了 LLM 模型的 scaling law,就是说随着模型规模逐步放大,任务的表现越来越好,如上图里的(a)图所示。这类任务通常符合如下共性:它们往往都是知识密集型任务,也就是说如果 LLM 模型包含的知识量越多,这类任务表现越好。而很多研究已经证明越大的 LLM 模型学习效率越高,也就是说相同训练数据量,模型越大任务效果越好,说明面对的即使是同样的一批训练数据,更大的 LLM 模型相对规模小一些的模型,从中学到了更多的知识。更何况一般情况下,在增大 LLM 模型参数的时候,往往会同步增加训练数据量,这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图,为何随着模型规模增大,这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务,其实都属于这种知识密集型任务,而很多任务在近两年获得了极大的效果提升,甚至超过了人类表现。很明显,这大概率是 LLM 模型的规模增长带来的,而非归功于某项具体的技术改进。</p>\n\n<p>第二类任务展现出 LLM 具备某种「<strong>涌现能力(Emergent Ability)</strong>」,如上图(b)所示。所谓「涌现能力」,指的是当模型参数规模未能达到某个阀值时,模型基本不具备解决此类任务的任何能力,体现为其性能和随机选择答案效果相当,但是当模型规模跨过阀值,LLM 模型对此类任务的效果就出现突然的性能增长。也就是说,模型规模是解锁(unlock)LLM 新能力的关键,随着模型规模越来越大,会逐渐解锁 LLM 越来越多的新能力。这是个很神奇的现象,因为它意味着如下让人对未来可报乐观预期的可能:或许很多任务,目前 LLM 还不能很好地解决,甚至站在现在这个时刻的我们看起来,LLM 完全没有能力解决这类任务,但因 LLM 具备「涌现能力」,所以如果我们继续推大模型,也许某一天它的这项能力就被突然解锁了。LLM 模型的规模增长会给我们带来意想不到的精彩礼物。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2206.04615\">《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》</a>这篇文章指出,这类<strong>体现出「涌现能力」的任务也有一些共性:这些任务一般由多步骤构成,要解决这些任务,往往需要先解决多个中间步骤,而逻辑推理能力在最终解决这类任务中发挥重要作用</strong>。思维链(Chain of Thought)Prompting 是典型的增强 LLM 推理能力的技术,能大幅提升此类任务的效果,关于 CoT 技术,在随后小节内容会做解释,此处暂不展开。</p>\n\n<p>问题是,为何 LLM 会出现这种「涌现能力」现象呢?上述文章以及<a href=\"https://arxiv.org/pdf/2206.07682\">《Emergent Abilities of Large Language Models》</a>给出了几个可能的解释:</p>\n\n<p>一种可能解释是<strong>有些任务的评价指标不够平滑</strong>。比如说有些生成任务的判断标准,它要求模型输出的字符串,要和标准答案完全匹配才算对,否则就是 0 分。所以,即使随着模型增大,其效果在逐步变好,体现为输出了更多的正确字符片段,但是因为没有完全对,只要有任何小错误都给 0 分,只有当模型足够大,输出片段全部正确才能得分。也就是说,因为指标不够平滑,所以不能体现 LLM 其实正在逐步改善任务效果这一现实,看起来就是「涌现能力」这种外在表现。</p>\n\n<p>另外一种可能的解释是:有些任务由若干中间步骤构成,随着模型规模增大,解决每个步骤的能力也在逐步增强,但是只要有一个中间步骤是错的,最终答案就是错的,于是也会导致这种表面的「涌现能力」现象。</p>\n\n<p>当然,<strong>上面的解释目前还都是猜想,至于为何 LLM 会出现这种现象,还需要进一步更深入的研究</strong>。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-8.jpeg\" alt=\"image\" /></p>\n\n<p>还有少部分任务,随着模型规模增长,任务的效果曲线展现出 U 形特性:随着模型规模逐渐变大,任务效果逐渐变差,但是当模型规模进一步增长,则效果开始越来越好,呈现出 U 形增长趋势,如上图所示的粉红色 PaLM 模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢?<a href=\"https://arxiv.org/pdf/2211.02011\">《Inverse scaling can become U-shaped》</a>这篇文章给出了一种解释:这些任务,内部其实隐含了两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。当模型规模小的时候,无法识别任意一种子任务,所以模型的表现跟随机选择答案差不多,当模型增长到中等规模的时候,主要执行的是干扰任务,所以对真正的任务效果有负面影响,体现为真正任务效果的下降,而当进一步增加模型规模,则 LLM 可以忽略干扰任务,执行真正的任务,体现为效果开始增长。</p>\n\n<p>对于那些随着模型规模增大,效果一直下降的任务,如果采用思维链(CoT)Prompting,则部分任务的表现转换为遵循 Scaling law,即模型规模越大效果越好,而其它任务则转换为U性增长曲线。这其实侧面说明了:此类任务应属于推理类型的任务,所以加入 CoT 后任务表现会发生质的变化。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:训练数据规模、模型参数规模和训练时长(步数),与最终 LLM 性能(loss 衡量)之间什么关系?<br />\n1、当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好。<br />\n2、训练数据量和模型参数是同等重要的。<br />\n3、我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么有些模型不遵循 scaling law?三类任务:<br />\n第一类完美遵循 scaling low。<br />\n第二类过了阈值后涌现。《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》和《Emergent Abilities of Large Language Models》认为是指标不平滑 or 中间步骤是错的。\n第三类 U 型,《Inverse scaling can become U-shaped》猜测可能两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。</p>\n</blockquote>\n\n<h2 id=\"四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</h2>\n\n<p>一般我们经常提到的人和 LLM 的接口技术包括:zero shot prompting、few shot prompting、In Context Learning,以及 Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献,会发现叫法比较乱。</p>\n\n<p>其中 Instruct 是 ChatGPT 的接口方式,就是说人以自然语言给出任务的描述,比如「把这个句子从中文翻译成英文」,类似这种。zero shot prompting 我理解其实就是现在的 Instruct 的早期叫法,以前大家习惯叫 zero shot,现在很多改成叫 Instruct。尽管是一个内涵,但是具体做法是两种做法。早期大家做 zero shot prompting,实际上就是不知道怎么表达一个任务才好,于是就换不同的单词或者句子,反复在尝试好的任务表达方式,这种做法目前已经被证明是在拟合训练数据的分布,其实没啥意思。目前 Instruct 的做法则是给定命令表述语句,试图让 LLM 理解它。所以尽管表面都是任务的表述,但是思路是不同的。</p>\n\n<p>而In Context Learning 和 few shot prompting 意思类似,就是给 LLM 几个示例作为范本,然后让LLM解决新问题。我个人认为 In Context Learning 也可以理解为某项任务的描述,只是 Instruct 是一种抽象的描述方式,In Context Learning 是一种例子示范的例子说明法。当然,鉴于目前这几个叫法用的有点乱,所以上述理解仅代表个人看法。</p>\n\n<p>所以我们此处只对 In Context Learning 和 Instruct 进行介绍,不再提 zero shot 和 few shot 了。</p>\n\n<h3 id=\"1神秘的-in-context-learning\">1、神秘的 In Context Learning</h3>\n\n<p>如果你细想,会发现 In Context Learning 是个很神奇的技术。它神奇在哪里呢?神奇在你提供给 LLM 几个样本示例,….,然后给它 x(n+1),LLM 竟然能够成功预测对应的 y(n+1)。听到这你会反问:这有什么神奇的呢?Fine-tuning 不就是这样工作的吗?你要这么问的话,说明你对这个问题想得还不够深入。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-9.jpeg\" alt=\"image\" /></p>\n\n<p>Fine-tuning 和 In Context Learning 表面看似都提供了一些例子给 LLM,但两者有质的不同(参考上图示意):Fine-tuning 拿这些例子当作训练数据,利用反向传播去修正 LLM 的模型参数,而修正模型参数这个动作,确实体现了 LLM 从这些例子学习的过程。但是,In Context Learning 只是拿出例子让 LLM 看了一眼,并没有根据例子,用反向传播去修正 LLM 模型参数的动作,就要求它去预测新例子。既然没有修正模型参数,这意味着貌似 LLM 并未经历一个学习过程,如果没有经历学习过程,那它为何能够做到仅看一眼,就能预测对新例子呢?这正是 In Context Learning 的神奇之处。这是否让你想起了一句歌词「只是因为在人群中多看了你一眼 再也没能忘掉你容颜」,而这首歌名叫「传奇」。你说传奇不传奇?</p>\n\n<p>看似 In Context Learning 没从例子里学习知识,实际上,难道 LLM 通过一种奇怪的方式去学习?还是说,它确实也没学啥?关于这个问题的答案,目前仍是未解之谜。现有一些研究各有各的说法,五花八门,很难判断哪个讲述的是事实的真相,甚至有些研究结论还相互矛盾。这里提供几个目前的说法,至于谁对谁错,只能你自己把握了。当然,我认为追求这个神奇现象背后的真相,是一个好的研究课题。</p>\n\n<p>试图证明 In Context Learning 没有从例子中学习的工作是<a href=\"https://arxiv.org/pdf/2202.12837\">《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》</a>。它发现了:在提供给 LLM 的样本示例中,yi 是否 xi 对应的正确答案,其实并不重要,如果我们把正确答案 yi 替换成随机的另外一个答案 yj ,这并不影响 In Context Learning 的效果。这起码说明了一点:In Context Learning 并没有提供给 LLM 那个从 x 映射到 y 的映射函数信息:y=f(x),否则的话你乱换正确标签,肯定会扰乱这个 y=f(x) 映射函数。也就是说,In Context Learning 并未学习这个输入空间到输出空间的映射过程。</p>\n\n<p>真正对 In Context Learning 影响比较大的是:x 和 y 的分布,也就是输入文本 x 的分布和候选答案 y 有哪些,如果你改变这两个分布,比如把 y 替换成候选答案之外的内容,则 In Context Learning 效果急剧下降。</p>\n\n<p>总之,这个工作证明了 In Context Learning 并未学习映射函数,但是输入和输出的分布很重要,这两个不能乱改。</p>\n\n<p>有些工作认为 LLM 还是从给出的示例学习了这个映射函数 y=f(x),不过是种隐式地学习。比如<a href=\"https://arxiv.org/pdf/2211.15661.pdf\">《What learning algorithm is in-context learning? Investigations with linear models》</a>认为 Transformer 能够隐式地从示例中学习 x 到 y 的映射过程,它的激活函数中包含了一些简单映射函数,而 LLM 通过示例能够激发对应的那一个。而<a href=\"https://arxiv.org/pdf/2212.10559\">《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》</a>这篇文章则将 ICL 看作是一种隐式的 Fine-tuning。</p>\n\n<p>总而言之,<strong>目前这还是一个未解之谜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】LLM 技术增量重点</strong>:In Context Learning</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:为什么 In Context Learning 有效?<br />\n1、《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》认为 ICL 没有从例子里学习 x 到 y 的映射关系,而只是学习了分布与分布的对应。<br />\n2、《What learning algorithm is in-context learning? Investigations with linear models》认为 ICL 隐式地学习了 x 到 y 的映射关系。<br />\n3、《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》认为 ICL 是隐式的 fine-tuning。</p>\n</blockquote>\n\n<h3 id=\"2神奇的-instruct-理解\">2、神奇的 Instruct 理解</h3>\n\n<p>我们可以把 Instruct 当作一种方便人类理解的任务表述,在这个前提下,目前关于 Instruct 的研究可以分成两种:偏学术研究的 Instruct,以及关于人类真实需求描述的 Instruct。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-10.jpeg\" alt=\"image\" /></p>\n\n<p>我们先来看第一种:偏学术研究的 Instruct。它的核心研究主题是多任务场景下,LLM 模型对 Instruct 理解的泛化能力。如上图中 FLAN 模型所示,就是说有很多 NLP 任务,对于每个任务,研究人员构造一个或者多个 Prompt 模版作为任务的 Instruct,然后用训练例子对 LLM 模型进行微调,让 LLM 以同时学习多个任务。训练好模型后,给 LLM 模型一个它没见过的全新任务的 Instruct,然后让 LLM 解决 zero shot 任务,从任务解决得是否足够好,来判断 LLM 模型是否有对 Instruct 理解的泛化能力。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】FLAN 是什么?</strong>2021 年 9 月 Google Research 团队在文章<a href=\"https://arxiv.org/pdf/2109.01652\">《Finetuned Language Models Are Zero-Shot Learners》</a>中提出的「Finetuned Language 」<br />\n1、FLAN 是 Finetuned LAnguage Net 的缩写,它用 Multi-task Learning 的方法和一种别出心裁的微调方式对 PLM 进行微调,在参数少 400 亿的情况下,性能超越 GPT-3。<br />\n2、部分参考自:https://juejin.cn/post/7064919723498012703 <br />\n3、FLAN 训练:对多个任务,把 Prompt 模板写成 Instruct,以此微调来学习。<br />\n4、FLAN 测试:新类型的任务,直接 zero-shot,判断 LLM 对 Instruct 理解的泛化能力。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】如何增加 LLM 模型 Instruct 泛化能力</strong> <br />\n1、增加训练任务的数量 <br />\n2、增加训练任务的类型多样性 <br />\n3、增加 LLM 模型参数规模 <br />\n4、提供 CoT Prompting</p>\n</blockquote>\n\n<p>如果归纳下目前的研究结论(可参考<a href=\"https://arxiv.org/pdf/2210.11416\">《Scaling Instruction-Finetuned Language Models》</a>/<a href=\"https://arxiv.org/pdf/2204.07705\">《Super-Natural Instructions: Generalization via Declarative Instructions on 1600+ NLP Tasks》</a>),能够有效增加 LLM 模型 Instruct 泛化能力的因素包括:增加多任务的任务数量、增加 LLM 模型大小、提供 CoT Prompting, 以及增加任务的多样性。如果采取任意一项措施,都可以增加 LLM 模型的 Instruct 理解能力。</p>\n\n<p>第二种是人类真实需求下的 Instruct,这类研究以 InstructGPT 和 ChatGPT 为代表。这类工作也是基于多任务的,但是和偏向学术研究类工作最大的不同,在于它是面向人类用户真实需求的。为什么这么说呢?因为它们用于 LLM 多任务训练的任务描述 Prompt,是从大量用户提交的真实请求中抽样而来的,而不是固定好研究任务的范围,然后让研究人员来写任务描述 prompt。这里所谓的「真实需求」,体现在两个方面:首先,因为是从用户提交的任务描述里随机抽取的,所以涵盖的任务类型更多样化,也更符合用户的真实需求;其次,某个任务的 prompt 描述,是用户提交的,体现了一般用户在表达任务需求时会怎么说,而不是你认为用户会怎么说。很明显,这类工作改出来的 LLM 模型,用户体验会更好。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2203.02155.pdf\">InstructGPT 论文</a>里,也拿这种方法和 FLAN 那种 Instruct based 方法做了比较。首先在 GPT3 上用 FLAN 提到的任务、数据以及 Prompt 模版进行微调,来在 GPT 3 上复现 FLAN 方法,然后和 InstructGPT 进行比较,因为 InstructGPT 的基础模型也是 GPT3,所以只有数据和方法的差别,两者可比,结果发现 FLAN 方法的效果,距离 InstructGPT 有很大的差距。那么背后的原因是什么呢?论文分析数据后认为,<strong>FLAN 方法涉及到的任务领域相对少</strong>,是 InstructGPT 涉及领域的子集,所以效果不好。也就是说,<strong>FLAN 论文里涉及到的任务和用户真实需求是不符的</strong>,而这导致在真实场景下效果不够好。而这对我们的启示是:从用户数据中收集真实需求,这事情是很重要的。</p>\n\n<blockquote>\n <p><strong>LLM 技术增量重点</strong>:Instruct <br />\n1、FLAN 的任务领域太少。<br />\n2、FLAN 不是从用户数据收集真实需求(研究人员构造任务),与用户真实需求不符。</p>\n</blockquote>\n\n<h3 id=\"3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</h3>\n\n<p>如果我们假设 In Context Learning 是用一些例子来具象地表达任务命令,Instruct 是一种更符合人类习惯的抽象任务描述。那么,一个很自然的问题是:它们之间有什么联系吗?比如,我们是否能够提供给 LLM 完成某个任务的若干具体示例,让 LLM 找出其对应的自然语言描述的 Instruct 命令?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-11.jpeg\" alt=\"image\" /></p>\n\n<p>目前有零星的工作在探索这个问题,我认为这个方向是很有研究价值的。先说答案,答案是:Yes,LLM Can。<a href=\"https://arxiv.org/pdf/2211.01910\">《Large Language Models Are Human-Level Prompt Engineers》</a>是做这个方向很有趣的工作,如上图所示,对于某项任务,给 LLM 一些示例,让 LLM 自动生成能够描述这项任务的自然语言命令,然后它再用 LLM 生成的任务描述去测试任务效果。它使用的基础模型是 GPT 3 和 InstructGPT,经过这项技术加持后,LLM 生成的 Instruct 的效果相比未采用这项技术的 GPT 3 以及 InstuctGPT 来说,指标有极大地提升,而且在一些任务上超过人类的表现。</p>\n\n<p>这说明了:<strong>具象的任务示例和任务的自然语言描述之间,有种神秘的内在联系。至于这种联系到底是什么?我们目前对此还一无所知</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:Instruct 和 ICL 之间的联系 <br />\n1、ICL 是给了一些具象例子的命令 <br />\n2、Instruct 相当于是抽象命令</p>\n</blockquote>\n\n<h2 id=\"五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</h2>\n\n<p>目前很多研究已证明 LLM 对于知识具有强大的记忆能力,但是,一般我们不会因为一个人记忆能力强,就说这人很聪明,是否具有强大的推理能力,往往是我们判断一个人是否聪明的重要标准。类似的,如果 LLM 的效果想让人觉得很惊艳,强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点,去推导出新知识或新结论。关于 LLM 的推理能力,是最近一年来 LLM 里最重要和热门的研究领域之一。于是,我们关心的问题就是:<strong>LLM 具备推理能力吗?如果具备,那么它的推理能力够强吗?</strong></p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:LLM 具备推理能力吗?</p>\n</blockquote>\n\n<p>这两个问题目前的答案似乎应该是:当模型规模足够大的时候,LLM 本身是具备推理能力的,在简单推理问题上,LLM 已经达到了很好的能力,但是复杂推理问题上,还需要更多深入的研究。</p>\n\n<p>如果梳理现有 LLM 推理相关工作的话,我把它们归到两大类,体现出挖掘或促进 LLM 推理能力不同的技术思路:第一类研究比较多,可以统称为<strong>基于 Prompt 的方法</strong>,核心思想是通过合适的提示语或提示样本,更好地激发出 LLM 本身就具备的推理能力,Google 在这个方向做了大量很有成效的工作。第二类做法是在<strong>预训练过程中引入程序代码</strong>,和文本一起参与预训练,以此进一步增强 LLM 的推理能力,这应该是 OpenAI 实践出的思路。比如 ChatGPT 肯定具备很强的推理能力,但它并不要求用户必须提供一些推理示例,所以 <strong>ChatGPT 强大的推理能力,大概率来源于使用代码参与 GPT 3.5 的预训练</strong>。</p>\n\n<p>这两种思路其实大方向是迥异的:利用代码增强 LLM 推理能力,这体现出一种通过增加多样性的训练数据,来直接增强 LLM 推理能力的思路;而基于 Prompt 的方法,它并不会促进 LLM 本身的推理能力,只是让 LLM 在解决问题过程中更好地展示出这种能力的技术方法。可以看出,前者(代码方法)治本,后者治标。当然,两者其实也是互补的,但从长远看,治本的方法更重要。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】挖掘或促进 LLM 推理能力的两个技术思路</strong>:<br />\n1、Google 有大量研究成果的基于 Prompt 的方法:对应 ICL,挖掘 LLM 的推理能力 —— 基于神奇的 ICL <br />\n2、OpenAI 实践出真知的策略 —— Pre-training 时引入程序代码</p>\n</blockquote>\n\n<h3 id=\"1基于-prompt-的方法\">1、基于 Prompt 的方法</h3>\n\n<p>这方面工作非常多,如果归纳一下的话,大致可以分为三条技术路线。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-12.jpeg\" alt=\"image\" /></p>\n\n<p>第一种思路是直接在问题上追加辅助推理 Prompt。这种方法简单直接,但在众多领域都很有效。这个做法是由<a href=\"https://arxiv.org/pdf/2205.11916\">《Large language models are zero-shot reasoners》</a>提出的,也被称为 zero-shot CoT。具体而言,分为两个阶段(如上图所示),第一阶段在提问的问题上追加「Let’s think step by step」这句提示语,LLM 会输出具体的推理过程;第二阶段,在第一阶段的问题后,拼接 LLM 输出的具体推理过程,并再追加 Prompt=“Therefore, the answer (arabic numerals) is”,此时 LLM 会给出答案。如此简单的操作,却可以大幅增加 LLM 在各项推理任务中的效果,比如在数学推理测试集 GSM8K 上,加上提示语后,推理准确率直接从原先的 10.4% 提升到了 40.4%,可谓神奇。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>加上「Let’s think step by step」立马就提升了 GSM8K 数学推理测试集的准确率 +30pt!</p>\n</blockquote>\n\n<p>为什么 LLM 会具备给一句「Let’s think step by step」提示语,就能列出详细的推理步骤并算出答案呢?其原因目前尚无定论,我的猜测是:<strong>很可能因为预训练数据里面存在大量的此种数据,就是以「Let’s think step by step」开头,然后后面是详细的推理步骤,最后给出答案,而 LLM 在预训练的时候记住了这些模式。而当我们输入这个提示语的时候,激发 LLM 模糊得「回忆」起某些例子的推导步骤,于是即可模仿这些例子进行步骤推理并给出答案</strong>。当然这只是我的无依据推论,若事实真的如此,如果你看过后面介绍的标准 CoT 做法,会发现 Zero-shot CoT 本质上和标准 CoT 很可能没什么区别,只是标准 CoT 由人工来写推理步骤的示例,而 Zero-shot CoT 大概率是通过提示语,激活了记忆中的某些包含推理步骤的示例,很可能是如此区别。而标准 CoT 效果比 Zero-Shot CoT 效果好也完全可以理解,因为毕竟靠 LLM 回忆示例,精准性估计不会太高,而人工给出的示例,准确性是有保障的,所以自然标准 CoT 效果会更好。</p>\n\n<p><strong>这侧面说明了一个道理,就是 LLM 本身是具备推理能力的</strong>,只是我们没有办法把它的这种能力激发出来而已,通过合适的提示语来进行两步提示,就在一定程度上可以释放出它的这种潜力。另外,对于中文,很可能存在另外一个黄金提示语,比如「详细解题思路如下」,类似这种,因为中文语料在讲解推理步骤的时候,经常用的引导句和「让我们一步一步来思考」应该是不同的,这是明显的西方说法,而探索出这个中文黄金提示语,其实也是很有必要的。</p>\n\n<p>第二种思路一般被称为基于示例的思维链(few-shot CoT, Chain of Thought)Prompting。这个方向目前是 LLM 推理研究的主方向,很多工作都是在这个思路上做的,我们简单介绍几个效果显著的代表性工作,基本能代表 CoT 的技术发展方向。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-13.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 的主体思想其实很直白;为了教会 LLM 模型学会推理,给出一些人工写好的推理示例,示例里把得到最终答案前,一步步的具体推理步骤说清楚,而这些人工写的详细推理过程,就是思维链 Prompting,具体例子可参照上图中蓝色文字部分。CoT 的意思是让 LLM 模型明白一个道理;<strong>就是在推理过程中,步子不要迈得太大,否则很容易出错,改变思维模式,化大问题为小问题,步步为营,积小胜为大胜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>这个过程已经从 programming computer 过渡成 teaching computer 了,训练 AI 越来越像一个需要人教育培养的孩子。<br />\nCoT 其实就是给一些思维链 step by step 的 prompting</p>\n</blockquote>\n\n<p>最早明确提出 CoT 这个概念的文章是<a href=\"https://arxiv.org/pdf/2201.11903\">《Chain of thought prompting elicits reasoning in large language models》</a>,论文发布于 22 年 1 月份,虽然做法很简单,但是应用 CoT 后 LLM 模型的推理能力得到了巨大提升,GSM8K 数学推理测试集准确率提高到 60.1% 左右。当然,这种给出详细推理步骤和中间过程的思想,并非 CoT 最早提出的,更早一些的「scratchpad」技术(可参考<a href=\"https://arxiv.org/pdf/2112.00114\">《Show Your Work: Scratchpads for Intermediate Computation with Language Models》</a>)首先采用了类似的思路。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-14.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 提出不久,很快在 22 年 3 月份,一项被称为「Self-Consistency」的改进技术就将 GSM8K 测试集准确率提高到 74.4%,提出这项改进的论文是<a href=\"https://arxiv.org/pdf/2203.11171\">《Self-Consistency Improves Chain of Thought Reasoning in Language Models》</a>。「Self-Consistency」的思路也很直观(参考上图):首先可以利用 CoT 给出几个写了推理过程的示例,然后要求 LLM 对给定的问题进行推理,如果是 CoT,直接输出一个推理过程和答案,整个过程就结束了。「Self-Consistency」则不然,它要求 LLM 输出多个不同的推理过程和答案,然后采用投票的方式选出最佳答案,思路非常简单直接,但是效果也确实好。「Self-Consistency」其实是教导 LLM 学会这么一个道理:孔乙己说过茴香豆的「茴」字有四种写法,类似的,一个数学题的正确解法也可以有很多种,每个不同的推导过程都指向最终的答案。条条大路通罗马,虽说也有个别迷路走到北京的,但是迷路的毕竟是少数,看看大多数人走到哪里,哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义,是不是这道理?</p>\n\n<p>再往后,<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>这个工作在「Self-Consistency」基础上,进一步集成了「从一个 Prompt 问题拓展到多个 Prompt 问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票」这三个改进点,将 GSM8K 测试集准确率提高到 83% 左右。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>GSM8K 数学推理测试集 <br />\n1、提高到 40.4%:加一句「Let’s think step by step」 <br />\n2、提高到 60.1%:应用 CoT 后,即训练时给 LLM 几个写了推理过程的示例 <br />\n3、提高到 74.7%:基于 CoT 的改进技术 Self-Consistency,给出多个不同推理过程和答案,投票选出最好答案 <br />\n4、提高到 83% 左右:基于 Self-Constistenty 的改进技术,1)一个 Prompt 拓展为多个 Prompt;2)检查推理中间步骤正确性;3)对多个输出的回答加权投票。</p>\n</blockquote>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-15.jpeg\" alt=\"image\" /></p>\n\n<p>第三种思路体现了一种分治算法的思想。当然这个所谓「分治」是我归纳的,别人没这么说。这种思路的核心思想是:对于一个复杂的推理问题,我们把它分解成若干容易解决的子问题,一一解决掉子问题后,我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得,这种思路可能才是揭示问题本质、最终解决 LLM 复杂推理问题正宗的道路。我们以「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术为例来说明这种思路的一种具体实现方式,如上图所示:它分为两个阶段,第一个阶段,从原始问题我们可以得知最终要问的问题是什么,我们假设最终问题是 Final Q,然后从原始问题填充 Prompt 模版「如果要解决 Final Q 问题,那么我需要先解决」,然后把原始问题和这个 Prompt 交给 LLM,让 LLM 模型给出答案,等于让 LLM 给出最终问题的前置子问题 Sub Q;接下来我们进入第二个阶段,让 LLM 先回答刚才拿到的子问题Sub Q,并拿到对应的答案,然后原始问题拼接子问题 Sub Q 及对应答案,再去问 LLM 最终那个问题 Final Q,此时LLM会给出最后的答案。如此这般,体现出拆解子问题,并从子问题的答案逐步找出最终答案的思路。</p>\n\n<h3 id=\"2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</h3>\n\n<p>以上是目前利用 Prompt 激发 LLM 模型推理能力的三种主流做法,而关于 LLM 的推理能力,目前还观察到一个有趣且费解的现象:除了文本外,如果能够加入程序代码一起参与模型预训练,则能大幅提升 LLM 模型的推理能力。这个结论从不少论文的实验部分都可以得出(可以参考<a href=\"https://arxiv.org/pdf/2210.03493\">《AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS》</a>/<a href=\"https://arxiv.org/pdf/2210.09261\">《Challenging BIG-Bench tasks and whether chain-of-thought can solve them》</a>等论文的实验部分)。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-16.jpeg\" alt=\"image\" /></p>\n\n<p>上图给出了一份实验数据,来自于论文<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>,其中 GPT3 davinci 就是标准的 GPT 3 模型,基于纯文本训练;code-davinci-002(OpenAI 内部称为 Codex)是同时在 Code 和 NLP 数据上训练的模型。如果比较两者效果,可以看出,不论采用具体哪种推理方法,仅仅是从纯文本预训练模型切换到文本和 Code 混合预训练模型,在几乎所有测试数据集合上,模型推理能力都得到了巨大的效果提升,比如我们以「Self Consistency」方法为例,在大多数据集合上的性能提升,都直接超过了 20 到 50 个百分点,这是很恐怖的性能提升,而其实在具体推理模型层面,我们什么也没做,仅仅是预训练的时候除了文本,额外加入了程序代码而已。</p>\n\n<p>除了这个现象,从上图数据中,我们还可以得出其它一些结论,比如 GPT 3 这种纯文本预训练模型,其实是具备相当程度的推理能力的,除了在 GSM8K 这种数学推理上效果比较差外,其它推理数据数据集合表现也还可以,前提你需要采用合适的方法,来激发出它本身就具备的这种能力;再比如,text-davinci-002,也就是在 code-davinci-002 基础上加入 instruct fine-tuning 后的模型(就是加入 InstructGPT 或 ChatGPT 模型的第一步),其推理能力要弱于 Codex,但是有其它研究表明它在自然语言处理任务又要强于 Codex。而这貌似说明了,加入 instruct fine-tuning,会损害 LLM 模型的推理能力,但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的,也能启发后续进一步的思考和探索。</p>\n\n<p>那么,一个自然的疑问是:<strong>为何预训练模型可以从代码的预训练中获得额外的推理能力?确切原因目前未知,值得深入探索</strong>。我猜测可能是因为原始版本的 Codex(只使用代码训练,可参考文献:<a href=\"https://arxiv.org/pdf/2107.03374\">《Evaluating Large Language Models Trained on Code》</a>)的代码训练是从文本生成代码,而且代码中往往包含很多文本注释,本质上这类似于预训练模型做了 <文本,Code> 两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释,很明显这些数学类或逻辑推理类的数据,对于解决下游数学推理问题是有帮助的,我猜大概率原因在此。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么预训练模型可以从代码预训练中获得额外的推力能力?</p>\n</blockquote>\n\n<h3 id=\"3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</h3>\n\n<p>上面介绍了 LLM 推理的主流技术思路和现有的一些结论,接下来谈谈我对 LLM 模型推理技术的思考,以下内容纯个人推断,没有太多证据,还请谨慎参考。我的判断是:虽然最近一年来,关于激发 LLM 的推理能力,这方面的技术进展很快,也取得了很大的技术进步,但是总体感觉是,我们可能走在正确的方向上,但是距离接触到真正的问题本质还有一段距离,对此要有更深入的思考和探索。</p>\n\n<p>首先,我比较赞同上述分治算法的主体思路,对于复杂的推理问题,我们应该把它拆解成若干简单的子问题,因为子问题对于 LLM 来说回答正确的概率就大很多,让 LLM 一一 回答子问题后,再逐步推导出最终答案。受到「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术的启发,如果进一步思考,我觉得 LLM 推理本质上很可能会是如下两种可能的其中之一:不断和 LLM 进行交互的图上推理问题,抑或是不断和LLM进行交互的程序流程图执行问题。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-17.jpeg\" alt=\"image\" /></p>\n\n<p>先说图上推理问题,如上图所示,假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构,图中的节点是子问题或者子步骤,图中的边代表了子问题之间的依赖关系,就是说只有回答好子问题 A,才能回答子问题 B,而且图中大概率存在循环结构,就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图,那么可以根据依赖关系,引导 LLM 一步一步按照图结构,回答必须首先回答的子问题,直到推导出最终答案。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-18.jpeg\" alt=\"image\" /></p>\n\n<p>再说程序流程图问题,参考上图,假设我们有办法把复杂问题拆解成子问题或子步骤,并产生一个由子步骤构成的类似程序流程图的结构,在这个结构里,有些步骤会反复执行多次(循环结构),有些步骤的执行需要进行条件判断(条件分支)。总而言之,在执行每个子步骤的时候和 LLM 进行交互,得到子步骤的答案,然后按照流程不断执行,直到输出最终答案。类似这种模式。假设这个思路大致正确的话,也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力:大概率因为 <文本,代码> 的多模态预训练模型,在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁,将两者联系起来的,即由文本描述到隐含的流程图,再映射到由流程图产生具体的代码。也就是说,这种多模态预训练,可以增强 LLM 模型从文本构建出隐含的流程图并按照流程图执行的能力,也就是加强了它的推理能力。</p>\n\n<p>当然,上述思路最大的问题是,我们如何根据文本描述的问题,能够靠 LLM 模型,或者其它模型,得到图结构或者流程图结构?这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练,走隐式学习内部隐含结构的方法。而目前的 CoT 技术,如果套到上述思路来思考的话,可以这么理解:标准 CoT,其实就是靠自然语言文本来描述图结构或者程序流程图的;而「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术,则是试图根据最后一个图节点,靠倒推来试图推导出其中的图结构,但是很明显,目前的方法限制了它倒推的深度,也就是说它只能推导出非常简单的图结构,这正是限制它能力的所在。</p>\n\n<h2 id=\"六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</h2>\n\n<p>这里列出一些我个人认为比较重要的 LLM 研究领域,或值得深入探索的研究方向。</p>\n\n<h4 id=\"探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</h4>\n\n<p>尽管继续推大 LLM 模型的规模,这事看似没有技术含量,但是其实这个事情异常重要。我个人判断,自从 Bert 出现以来,到 GPT 3,再到 ChatGPT,大概率这些给人印象深刻的关键技术突破,核心贡献都来自于 LLM 模型规模的增长,而非某项具体技术。说不定,揭开 AGI 真正的钥匙就是:超大规模及足够多样性的数据、超大规模的模型,以及充分的训练过程。再者,做超大规模的 LLM 模型,对技术团队的工程实现能力要求是非常高的,也不能认为这事情缺乏技术含量。</p>\n\n<p>那么继续推大 LLM 模型规模,有什么研究意义呢?我觉得有两方面的价值。首先,如上所述,我们已知,对于知识密集型的任务,随着模型规模越大,各种任务的效果会越来越好;而对很多推理类型的有难度的任务,加上 CoT Prompting 后,其效果也呈现出遵循 Scaling law 的趋向。那么,很自然的一个问题就是:对于这些任务,LLM 的规模效应,能将这些任务解决到何种程度?这是包括我在内,很多人关心的问题。其次,考虑到 LLM 具备的神奇的「涌现能力」,如果我们继续增加模型规模,它会解锁哪些让我们意想不到的新能力呢?这也是很有意思的问题。考虑到以上两点,我们仍然需要不断增大模型规模,看看模型规模对解决各类任务的天花板在哪里。</p>\n\n<p>当然,这种事情也就只能说说,对 99.99% 的从业者来说,是没有机会和能力做这个事情的。要做这个事情,对研究机构的财力及投入意愿、工程能力、技术热情,都有极高的要求,缺一不可。能做这事情的机构,粗估下来,国外不超过 5 家,国内不超过 3 家。当然,考虑到成本问题,未来也许会出现「股份制大模型」,就是有能力的几家机构合作,群策群力,一起来共建超级大模型的现象。</p>\n\n<h4 id=\"增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</h4>\n\n<p>正如之前对 LLM 推理能力的叙述,尽管 LLM 在最近一年推理能力得到了很大的提升,但是很多研究(参考<a href=\"https://arxiv.org/pdf/2208.05051\">《Limitations of Language Models in Arithmetic and Symbolic Induction》</a>/<a href=\"https://arxiv.org/pdf/2206.10498\">《Large Language Models Still Can’t Plan》</a>)表明,目前 LLM 能够解决得比较好的推理问题,往往都相对简单,LLM 的复杂推理能力仍然薄弱,比如即使是简单的字符拷贝推理或者加减乘除运算,当字符串或者数字非常长的时候,LLM 推理能力会极速下降,再比如行为规划能力等复杂推理能力很弱。总而言之,加强 LLM 的复杂推理能力,应该是 LLM 未来研究中最重要的环节之一。</p>\n\n<p>前文有述,<strong>加入代码加入预训练,这是一种直接增强 LLM 推理能力的方向。这个方向目前研究尚显不足,更像是实践经验的总结</strong>,探索背后的原理,并进而引入更多类型除代码外的新型数据来增强 LLM 的推理能力,这可能是更本质提升推理能力的方向。</p>\n\n<h4 id=\"llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</h4>\n\n<p>目前的 ChatGPT 擅长 NLP 和 Code 任务,作为通向 AGI 的重要种子选手,<strong>将图像、视频、音频等图像与多模态集成进入 LLM,乃至 AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入 LLM</strong>,是 LLM 通往 AGI 的必经之路。而这个方向才刚刚开始,因此具备<strong>很高的研究价值</strong>。</p>\n\n<h4 id=\"更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</h4>\n\n<p>如前所述,ChatGPT 的最大技术贡献即在此。但是很明显,目前的技术并不完美,肯定还有很多命令 LLM 理解不了。所以,沿着这个方向,寻找更好的技术,<strong>来让人类使用自己习惯的命令表达方式,而 LLM 又能听懂,这是个新的,且非常有前景的技术方向</strong>。</p>\n\n<h4 id=\"建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</h4>\n\n<p>好的评测数据集,是引导技术不断进步的基石。随着 LLM 模型逐步增大,任务效果快速提升,导致很多标准测试集快速过时。也就是说,这些数据集合相对现有技术来说,太容易了,在没有难度的测试集合下,我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合,是促进 LLM 技术进步的关键所在。</p>\n\n<p>目前行业应出现了一些新的测试集,有代表性的包括 BIGBench、OPT-IML 等。这些测试集合体现出一些特性,比如相对 LLM 现有技术具备一定的难度、综合了各种各样多种类型的任务等。</p>\n\n<p>受到 ChatGPT 的启发,我觉得除此外应纳入另一考虑因素:体现真实用户需求。就是说,这些任务的表述由用户真实发起,这种方式构建出来的 LLM 模型,才能解决用户实际需求。</p>\n\n<p>除此外,相信 LLM 会快速将能力溢出到 NLP 之外的领域,而如何融入更多其它领域的评测数据,也是需要提前去考虑。</p>\n\n<h4 id=\"高质量数据工程\">高质量数据工程</h4>\n\n<p>对于预训练模型来说,数据是其根本,预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此,我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。</p>\n\n<p>关于数据,需要考虑两个方面:数据的质量和数量。而根据 T5 的对比实验,我们可以得出结论:在数量和质量两个因素里,质量优先,正确的道路应该是在保证数据质量的前提下,再去增大数据规模。</p>\n\n<p>数据质量,包括数据的信息含量以及数据的多样性等多个衡量标准,比如 Wiki 明显就属于世界知识密度极高的高质量数据,这是从信息含量来说的;而增加数据类型的多样性,无疑是激发 LLM 各种新能力的根本,比如加入问答网站的数据,对于 LLM 的 QA 能力提升是有直接帮助的。多样化的数据赋予了 LLM 更好解决更多不同类型任务的能力,所以,这可能是数据质量里最关键的标准。</p>\n\n<p>关于数据数量,原则上互联网上公开发布的数据都可以纳入 LLM 模型的预训练过程。那么,它的极限在哪里?<a href=\"https://arxiv.org/pdf/2211.04325\">《Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning》</a>对此进行了估算,结论是到 2026 年左右,高质量的NLP数据将会用光,低质量 NLP 数据会在 2030 到 2050 年用光,而低质量图像数据会在 2030 到 2060 年用光。而这意味着:要么到时我们有新类型的数据源,要么我们必须增加 LLM 模型对数据的利用效率。否则,目前这种数据驱动的模型优化方式将会停止进步,或者收益减少。</p>\n\n<h4 id=\"超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</h4>\n\n<p>目前规模最大的 LLM 中,有相当比例的模型采取了稀疏(Sparse)结构,比如 GPT 3、PaLM、GLaM 等,GPT 4 大概率也会走稀疏模型路线。之所以采用 Sparse 化的模型,主要好处是它可以极大减少 LLM 的训练时间和在线推理时间。Switch Transformer 论文里指出:在相同算力预算的前提下,使用稀疏化 Transformer,相对 Dense Transformer,LLM 模型的训练速度可以提升 4 倍到 7 倍。为何 Sparse 模型可以加快训练和推理时间呢?这是因为尽管模型参数巨大,但是对于某个训练实例,Sparse 模型通过路由机制,只使用整个参数中的一小部分,参与训练和推理的活跃参数量比较少,所以速度快。</p>\n\n<p>我认为未来超大的 LLM 模型大概率会收敛到稀疏模型。主要有两个原因:一方面,现有研究表明(参考<a href=\"https://arxiv.org/pdf/2210.06313\">《Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers》</a>),标准的 Dense Transformer在训练和推理时,它本身也是稀疏激活的,就是说只有部分参数会被激活,大部分参数没有参与训练和推理过程。既然这样,我们不如直接迁移到稀疏模型;另外,毫无疑问 LLM 模型的规模会继续推大,而高昂的训练成本是妨碍其进一步扩大模型的重要阻力,<strong>使用稀疏模型可以极大降低超大模型的训练成本</strong>,所以随着模型规模越大,稀疏模型带来的收益越明显。考虑到这两个方面,大概率未来更大的 LLM 模型会采用稀疏模型方案。</p>\n\n<p>那为何目前其它大规模模型不走稀疏模型的路线呢?因为 Sparse 模型存在训练不稳定、容易过拟合等问题,不太容易训练好。所以,如何修正稀疏模型面临的问题,<strong>设计出更容易训练的稀疏模型,是很重要的未来研究方向</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>重要研究拓展方向 <br />\n1、任务层 —— 增加 LLM 推理能力 <br />\n2、接口层 —— 命令更自然:怎么把 ICL 逐渐过渡成 zero-shot 的 Instruct <br />\n3、任务层 —— 多模态拓展:先在 NLP 和 Code 上奔向 AGI,把 CV、音频、AI for science、自动驾驶、机器人逐渐囊括进来 <br />\n4、模型层 —— 训练更容易:稀疏矩阵问题很多(训练不稳定、容易过拟合)的解决</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>为什么稀疏结构效果好?<br />\n1、计算快:如果很稠密,则对很多知识的提炼都耦合在了一起。如果比较稀疏,就类似人脑,对不同功能、不同知识等内容是分开存储的,耦合少、计算速度就快。<br />\n2、好训练:越稀疏训练成本越低\n3、缺点:训练不稳定,容易过拟合</p>\n</blockquote>\n\n<h2 id=\"七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</h2>\n\n<p>如果希望能复刻类似 ChatGPT 这种效果令人惊艳的 LLM 模型,综合目前的各种研究结论,在做技术选型时需要重点权衡如下问题:</p>\n\n<p>首先,在预训练模式上,我们有三种选择:GPT 这种自回归语言模型,Bert 这种双向语言模型,以及 T5 这种混合模式(Encoder-Decoder 架构,在 Encoder 采取双向语言模型,Decoder 采取自回归语言模型,所以是一种混合结构,但其本质仍属于 Bert 模式)。我们应选择 GPT 这种自回归语言模型,其原因在本文范式转换部分有做分析。目前看,<strong>国内 LLM 在做这方面技术选型的时候,貌似很多都走了 Bert 双向语言模型或 T5 混合语言模型的技术路线,很可能方向走偏了</strong>。</p>\n\n<p>第二,<strong>强大的推理能力是让用户认可 LLM 的重要心理基础</strong>,而如果希望 LLM 能够具备强大的推理能力,根据目前经验,最好在做预训练的时候,要引入大量代码和文本一起进行 LLM 训练。至于其中的道理,在本文前面相关部分有对应分析。</p>\n\n<p>第三,如果希望模型参数规模不要那么巨大,但又希望效果仍然足够好,此时有两个技术选项可做配置:要么增强高质量数据收集、挖掘、清理等方面的工作,意思是我模型参数可以是 ChatGPT / GPT 4 的一半,但是要想达到类似的效果,那么高质量训练数据的数量就需要是 ChatGPT/GPT 4 模型的一倍(Chinchilla 的路子);另外一个可以有效减小模型规模的路线是采取文本检索(Retrieval based)模型 + LLM 的路线,这样也可以在效果相当的前提下,极大减少 LLM 模型的参数规模。这两个技术选型不互斥,反而是互补的,也即是说,可以同时采取这两个技术,在模型规模相对比较小的前提下,达到超级大模型类似的效果。</p>\n\n<p>第四,超级大模型因为模型规模大,所以训练成本过高,导致很少有机构有能力去做这件事。而且由上文分析可见,继续不断推大 LLM 模型规模是肯定会发生、也应该去做的事情。于是,如何通过技术手段降低 LLM 的训练成本就很重要。LLM 的特征抽取器 Sparse 化是有效降低模型训练及推理成本的技术选择。由此可见,随着模型越来越大,LLM 模型 Sparse 化是一个应该考虑的选项。</p>\n\n<p>第五,ChatGPT 是目前最接近理想 LLM 的技术方案,而理想中的 LLM 应该是以一个几乎无所不能的基础通用大模型作为依托,来支持各种各样的上层任务类型。目前看,支持越来越多的任务类型,主要是通过增加 LLM 预训练数据的多样性来达成的,数据多样性越好,LLM 能够支持的任务类型就越丰富。所以,<strong>应该重视通过增加数据多样性来增加 LLM 新能力的思路</strong>。</p>\n\n<p>第六,易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务,而 LLM 要能够理解这些 Instruct 的真实含义。另外,也要注意这些 Instruct 是符合人类真实需求的,就是说,要从最终用户那里收集任务表述方式,而不能靠研发人员自己的臆想或猜测。ChatGPT 给我最大的启发其实是这一点,至于是否用增强学习我倒觉得不重要,其它替代技术应该也能做类似的事情。</p>\n\n<h2 id=\"八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</h2>\n\n<p>为什么是 OpenAI 作出了 ChatGPT,而不是其它机构呢?我们在这里可以做个简单分析。</p>\n\n<p>在本文开头,我们提到了 OpenAI 看待 LLM 的理念。OpenAI 是怎么看待 LLM 的呢?回顾它不断推出的技术,可以看出,它其实从 GPT 1.0 开始,基本就坚定地把 LLM 看做是通往 AGI 的一条必由之路。具体而言,在 OpenAI 眼中,未来的 AGI 应该长这个样子:有一个任务无关的超大型 LLM,用来从海量数据中学习各种知识,这个 LLM 以生成一切的方式,来解决各种各样的实际问题,而且它应该能听懂人类的命令,以便于人类使用。其实对 LLM 发展理念的理解,在前半部分,就是「构建一个任务无关的超大型 LLM,让它从海量数据中学习各种知识」,这一点几乎是大家的共识,能体现出 OpenAI 眼光的其实是后半部分。</p>\n\n<p>OpenAI 的理念比较超前,对自我定位从一开始就定得比较高,始终坚定不移地探索上述方式是否可以实现 AGI。OpenAI 之所以能作出 ChatGPT,胜在一个是定位比较高,另一个是不受外界干扰,态度上坚定不移。</p>\n\n<p>我们可以回顾下它走的一些关键路程:GPT 1.0 走的是生成模式的自回归语言模型路线,比 Bert 出来的还早些。Bert 证明了:双向语言模型对于很多 NLP 理解类任务,效果比自回归这种单向语言模型效果更好。尽管如此,GPT 2.0 并没有因此切换到双向语言模型这条路上,仍然走文本生成的路,而且开始尝试零示例(zero shot)prompt 和少量示例(few shot)prompt。其实这时候, OpenAI 心目中的 AGI 已经开始浮出水面,逐渐显示出轮廓了。只是因为 zero shot/few shot 效果比 Bert+fine-tuning 差的比较远,所以大家都没太当回事,甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候,我估计即使是 OpenAI 自己,也不一定能确保这条路肯定能走通。</p>\n\n<p>但是,这不妨碍它继续在这条路上往后走。GPT 3.0 已经展示出了比较强大的 zero shot/few shot prompt 能力,这时候 OpenAI 心目中的 AGI 已经完全漏出水面,轮廓清晰,而且它的效果也证明了这条路,是有较大可能走得通的。GPT 3.0 是一个决定 LLM 发展方向的叉路口和分水岭,与之对应的另外一条路是「Bert+fine-tuning」模式。在这个岔路口,不同的从业者选择走上了不同的道路,后面的技术差距也是从这里开始拉开的。很遗憾地是,国内很多从业者选择继续在「Bert+fine-tuning」这条路上往后走,这也是造成今天落后局面的一个关键时间节点。再往后,就是 InstructGPT 和 ChatGPT 了,OpenAI 通过 ChatGPT 证明了一点;虽然我们距离真正的 AGI,可能还有很长的路要走,但是通过超大 LLM 走向 AGI 这条路,目前看是可行的。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Transformer":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</h2>\t\t\n\t<time datetime=\"2023-01-08T18:13:09+00:00\" class=\"by-line\">08 Jan 2023, 杭州 | 麦克船长 | 总计 40590 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-1.jpeg\" alt=\"image\" /></p>\n\n<p>原文链接:<a href=\"https://zhuanlan.zhihu.com/p/597586623\">https://zhuanlan.zhihu.com/p/597586623</a></p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一潮流之巅nlp-研究范式的转换\" id=\"markdown-toc-一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</a> <ul>\n <li><a href=\"#1范式转换-10从深度学习到两阶段预训练模型\" id=\"markdown-toc-1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</a> <ul>\n <li><a href=\"#11影响一中间任务的消亡\" id=\"markdown-toc-11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</a></li>\n <li><a href=\"#12影响二不同研究方向技术路线的统一\" id=\"markdown-toc-12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</a></li>\n </ul>\n </li>\n <li><a href=\"#2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\" id=\"markdown-toc-2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</a> <ul>\n <li><a href=\"#21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\" id=\"markdown-toc-21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</a></li>\n </ul>\n </li>\n <li><a href=\"#影响一让-llm-适配人的新型交互接口\" id=\"markdown-toc-影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</a></li>\n <li><a href=\"#影响二很多-nlp-子领域不再具备独立研究价值\" id=\"markdown-toc-影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</a></li>\n <li><a href=\"#影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\" id=\"markdown-toc-影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</a></li>\n </ul>\n </li>\n <li><a href=\"#二学习者从无尽数据到海量知识\" id=\"markdown-toc-二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</a> <ul>\n <li><a href=\"#1求知之路llm-学到了什么知识\" id=\"markdown-toc-1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</a></li>\n <li><a href=\"#2记忆之地llm-如何存取知识\" id=\"markdown-toc-2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</a></li>\n <li><a href=\"#3知识涂改液如何修正-llm-里存储的知识\" id=\"markdown-toc-3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</a></li>\n </ul>\n </li>\n <li><a href=\"#三规模效应当-llm-越来越大时会发生什么\" id=\"markdown-toc-三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</a></li>\n <li><a href=\"#四人机接口从-in-context-learning-到-instruct-理解\" id=\"markdown-toc-四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</a> <ul>\n <li><a href=\"#1神秘的-in-context-learning\" id=\"markdown-toc-1神秘的-in-context-learning\">1、神秘的 In Context Learning</a></li>\n <li><a href=\"#2神奇的-instruct-理解\" id=\"markdown-toc-2神奇的-instruct-理解\">2、神奇的 Instruct 理解</a></li>\n <li><a href=\"#3in-context-learning-和-instruct-的联系\" id=\"markdown-toc-3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</a></li>\n </ul>\n </li>\n <li><a href=\"#五智慧之光如何增强-llm-的推理能力\" id=\"markdown-toc-五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</a> <ul>\n <li><a href=\"#1基于-prompt-的方法\" id=\"markdown-toc-1基于-prompt-的方法\">1、基于 Prompt 的方法</a></li>\n <li><a href=\"#2代码预训练增强-llm-推理能力\" id=\"markdown-toc-2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</a></li>\n <li><a href=\"#3关于-llm-推理能力的思考\" id=\"markdown-toc-3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</a></li>\n </ul>\n </li>\n <li><a href=\"#六未来之路llm-研究趋势及值得研究的重点方向\" id=\"markdown-toc-六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</a> <ul>\n <li><a href=\"#探索-llm-模型的规模天花板\" id=\"markdown-toc-探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</a></li>\n <li><a href=\"#增强-llm-的复杂推理能力\" id=\"markdown-toc-增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</a></li>\n <li><a href=\"#llm-纳入-nlp-之外更多其它研究领域\" id=\"markdown-toc-llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</a></li>\n <li><a href=\"#更易用的人和-llm-的交互接口\" id=\"markdown-toc-更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</a></li>\n <li><a href=\"#建设高难度的综合任务评测数据集\" id=\"markdown-toc-建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</a></li>\n <li><a href=\"#高质量数据工程\" id=\"markdown-toc-高质量数据工程\">高质量数据工程</a></li>\n <li><a href=\"#超大-llm-模型-transformer-的稀疏化\" id=\"markdown-toc-超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</a></li>\n </ul>\n </li>\n <li><a href=\"#七取经之路复刻-chatgpt-时要注意些什么\" id=\"markdown-toc-七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</a></li>\n <li><a href=\"#八chatgpt为什么是-openai\" id=\"markdown-toc-八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</a></li>\n</ul>\n\n<p>ChatGPT 出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型(LLM,Large Language Model)效果能好成这样;惊醒是顿悟到我们对LLM的认知及发展理念,距离世界最先进的想法,差得有点远。我属于既惊喜又惊醒的那一批,也是典型的中国人,中国人善于自我反思,于是开始反思,而这篇文章正是反思的结果。</p>\n\n<p>实话实说,国内在 LLM 模型相关技术方面,此刻,距离最先进技术的差距进一步加大了。技术领先或技术差距这事情,我觉得要动态地以发展的眼光来看。<strong>在 Bert 出现之后的一到两年间,其实国内在这块的技术追赶速度还是很快的,也提出了一些很好的改进模型,差距拉开的分水岭应该是在 GPT 3.0 出来之后,也就是 2020 年年中左右</strong>。在当时,其实只有很少的人觉察到:GPT 3.0 它不仅仅是一项具体的技术,其实体现的是 LLM 应该往何处去的一个发展理念。自此之后,差距拉得越来越远,ChatGPT 只是这种发展理念差异的一个自然结果。所以,我个人认为,抛开是否有财力做超大型 LLM 这个因素,如果单从技术角度看,差距主要来自于对 LLM 的认知以及未来应往何处去的发展理念的不同。</p>\n\n<p>国内被国外技术甩得越来越远,这个是事实,不承认也不行。前阵子网上很多人担忧说国内 AI 现在处于「危急存亡之秋」,我觉得倒也不至于这么严重。君不见,这个世界上,具备这么超前眼光的只有 OpenAI 一家吗?<strong>包括 Google 在内,其实对于 LLM 发展理念的理解,明显都落后 OpenAI 一个身位。现实是 OpenAI 表现过于优秀,把所有人都甩开了,不仅仅是国内</strong>。</p>\n\n<p>我觉得,OpenAI 对 LLM 在理念及相关技术方面,领先国外的 Google、DeepMind 大约半年到一年的时间,领先国内大概两年左右的时间。在 LLM 这个事情上,感觉梯队很明显,Google 应该是排在第二位,最能体现 Google 技术眼光的是 PaLM 和 Pathways,推出时间大概在 22 年 2 月到 4 月间,同一时期,OpenAI 推出的却是 InstructGPT,从这里就可以看出 Google 和 OpenAI 的差距了,至于为何这么说,你看了我后面的正文后大概能理解。DeepMind 之前的重心一直在强化学习攻克游戏和 AI for science 这些方面,切入LLM 其实很晚,应该是21 年才开始重视这个方向,目前也处于追赶状态。Meta 就更不用说了,重心一直不在 LLM 上,目前感觉也发力开始追赶。这还是目前做得最好的一批机构,尚且如此,更何况国内呢?我觉得情有可原。至于 OpenAI 关于 LLM 的理念是什么,我在本文的最后一部分,会谈谈我的认知。</p>\n\n<p>本文梳理自 GPT 3.0 出现之后的主流 LLM 技术,能够让您对 LLM 领域的技术脉络,LLM 技术发展过程中出现过的不同发展理念,乃至未来可能的发展趋势,有比较清晰的认知。当然,很多地方讲的内容是我个人看法,有很大的主观性,错漏难免,所以还请谨慎参考。</p>\n\n<p>本文试图回答下面一些问题:ChatGPT 是否带来了 NLP 乃至 AI 领域的研究范式转换?如果是,那会带来怎样的影响?LLM 从海量数据中学到了什么知识?LLM 又是如何存取这些知识的?随着LLM规模逐步增大,会带来什么影响?什么是 In Context Learning?为什么它是一项很神秘的技术?它和 Instruct 又是什么关系?LLM 具备推理能力吗?思维链 CoT 又是怎么做的?等等,相信看完,能让您对这些问题有一个答案。</p>\n\n<p>首先,在谈 LLM 技术现状前,先宏观地谈下我心目中的研究范式转换问题。这样,我们才能「先见森林,再见树木」,对具体技术为何会是如此变化有个更清晰的认知。</p>\n\n<h2 id=\"一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</h2>\n\n<p>如果我们把时间线往前拉得更长一些,回到 NLP 领域的深度学习时代,在更长时间窗口内观察技术变迁及其影响,可能会更容易看清其中的一些关键节点。我个人认为,在最近 10 年来NLP领域的技术发展过程中,可能存在两次大的研究范型转换。</p>\n\n<h3 id=\"1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在深度学习引入 NLP 领域(2013 年左右),到 GPT 3.0 出现之前(2020 年 5 月左右)。</p>\n\n<p>在 Bert 和 GPT 模型出现之前,NLP 领域流行的技术是深度学习模型,而 NLP 领域的深度学习,主要依托于以下几项关键技术:以大量的改进 LSTM 模型及少量的改进 CNN 模型作为典型的特征抽取器;以 Sequence to Sequence(或叫 encoder-decoder 亦可)+ Attention 作为各种具体任务典型的总体技术框架。</p>\n\n<p>在这些核心技术加持下,NLP 领域深度学习的主要研究目标,如果归纳一下,是如何有效增加模型层深或模型参数容量。就是说,怎么才能往 encoder 和 decoder 里不断叠加更深的 LSTM 或 CNN 层,来达成增加层深和模型容量的目标。这种努力,尽管确实不断增加了模型层深,但是从解决具体任务的效果角度看,总体而言,不算很成功,或者说和非深度学习方法相比,带来的优势不算大。</p>\n\n<p>深度学习之所以不够成功,我认为主要原因来自于两个方面:一方面是某个具体任务有限的训练数据总量。随着模型容量的增加,需要靠更大量的训练数据来支撑,否则即使你能把深度做起来,任务效果也做不上去。而在预训练模型出现之前,很明显这是 NLP 研究领域一个严重问题;另外一个方面是 LSTM/CNN 特征抽取器,表达能力不够强。意思是就算给你再多的数据也没用,因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因,阻碍了深度学习在 NLP 领域的成功突围。</p>\n\n<p>Bert / GPT 这两个预训练模型的出现,无论在学术研究角度看,还是工业应用角度来看,都代表了 NLP 领域的一个技术飞跃,并带来了整个领域研究范式的转换。这种范式转换带来的影响,体现在两个方面:首先,是部分 NLP 研究子领域的衰退乃至逐步消亡;其次,NLP 不同子领域的技术方法和技术框架日趋统一,在 Bert 出现后一年左右,技术栈基本收敛到两种技术模式中。关于这两点,我们分头来谈。</p>\n\n<h4 id=\"11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</h4>\n\n<p>NLP 是一个宏观研究领域的统称,里面有五花八门具体的子领域与子方向,如果仔细分析,从任务的性质角度,可以把这些任务分成两大类:一类可以叫做「中间任务」,一类可以称为「最终任务」。</p>\n\n<p>典型的中间任务包括:中文分词、词性标注、NER、句法分析、指代消解、语义 Parser 等,这类任务一般并不解决应用中的实际需求,大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的,比如几乎没有需求说,我要一个句法 Parser,把这个句子的句法分析树给用户看看,用户不需要看到这些NLP的中间阶段处理结果,他只关心某个具体任务你有没有干好。「最终任务」包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等,有很多。这类任务的特点是每个子领域都解决某个实际需求,任务结果基本能直接呈现给用户,比如用户确实存在给你一句英文,告诉他中文是什么的需求。</p>\n\n<p>按理说,「中间任务」就不应该出现,而之所以会存在,这是 NLP 技术发展水平不够高的一种体现。在技术发展早期阶段,因为当时的技术相对落后,很难一步做好有难度的最终任务。比如机器翻译,早期技术要做好机器翻译是很困难的,于是科研人员就把难题分而治之,分解成分词、词性标注、句法分析等各种中间阶段,先把每个中间阶段做好,然后再拼起来完成最终任务,这也是没办法的事情。</p>\n\n<p>但是自从 Bert/GPT 出现之后,其实就没有必要做这些中间任务了,因为通过大量数据的预训练,Bert/GPT 已经把这些中间任务作为语言学特征,吸收到了 Transformer 的参数里,此时我们完全可以端到端地直接解决那些最终任务,而无须对这种中间过程专门建模。这里可能争议最大的是中文分词,其实道理也是一样的,哪些字应该组成一个词,这个其实你不用管,让 LLM 自己当特征去学就行了,只要对于解决任务有帮助,它自然会去学该学的合理分词方式,也未必一定要和我们人类理解的分词规则相同。</p>\n\n<p>基于以上认知,其实在Bert/GPT一出现,你就应该得出这类NLP的中间阶段的任务,会逐步退出历史舞台这个结论。</p>\n\n<h4 id=\"12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</h4>\n\n<p>在说明具体影响前,我们先讨论下另外一种 NLP 任务划分方式,这对于理解后面内容有帮助。如果对「最终任务」进一步进行分类,又大致可以分为两大不同类型的任务:自然语言理解类任务和自然语言生成类任务。如果排除掉「中间任务」的话,典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等,这种任务本质上都是分类任务,就是说输入一个句子(文章),或者两个句子,模型参考所有输入内容,最后给出属于哪个类别的判断。自然语言生成也包含很多 NLP 研究子方向,比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本,对应地,模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上。</p>\n\n<p>自从 Bert/GPT 模型诞生后,出现了明显的技术统一趋向。首先,NLP 中不同的子领域,其特征抽取器都逐渐从 LSTM/CNN 统一到 Transformer 上。其实,自Bert公开后不久,就应该意识到,这必然会成为技术趋势。而且,目前 Transformer 不仅统一了 NLP 诸多领域,也正在逐步地替换图像处理各种任务中被广泛使用的 CNN 等其它模型的进程之中,类似的,多模态模型目前也基本都采用了 Transformer 模型。这种Transformer从NLP出发,攻城略地逐步统一AI越来越多领域的趋势,起始于 2020 年底出现的 Vision Transformer (ViT) ,之后蓬勃发展,到目前已大获成功,且其继续向更多领域拓展的势头会越来越迅猛。</p>\n\n<p>其次,大多数 NLP 子领域的研发模式切换到了两阶段模式:模型预训练阶段 + 应用微调(Fine-tuning)或应用 Zero/Few Shot Prompt 模式。更准确地说,NLP 各种任务其实收敛到了两个不同的预训练模型框架里:对于自然语言理解类任务,其技术体系统一到了以 Bert 为代表的「双向语言模型预训练 + 应用 Fine-tuning」模式;而对于自然语言生成类任务,其技术体系则统一到了以GPT 2.0 为代表的「自回归语言模型(即从左到右单向语言模型)+ Zero/Few Shot Prompt」模式。至于为何会分化成两条技术路线,有其必然性,关于这点我们放在后面解释。</p>\n\n<p>这两种模式,看似比较相像,但其背后蕴含了迥异的发展思路,也会导向不同的未来发展方向。不过遗憾的是,我们中的绝大多数人,在当时都低估了GPT 这条发展路线的潜力,而把视觉中心聚焦到了Bert这种模式上。</p>\n\n<h3 id=\"2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在 GPT 3.0 出现之后(20 年 6 月左右),一直到目前为止,我们应该正处于这个范式转换过程中。</p>\n\n<p>ChatGPT 是触发这次范型转换的关键节点,但是在 InstructGPT 出现之前,其实 LLM 处于这次范式转换前的一个过渡期。</p>\n\n<h4 id=\"21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</h4>\n\n<p>前面说过,在预训练模型发展的早期,技术框架收敛到了 Bert 模式和 GPT 模式这两种不同的技术范型,而且人们普遍更看好 Bert 模式一些,相当多数的后续技术改进,都是沿着 Bert 那条路走的。但是,随着技术的继续发展,你会发现,目前规模最大的 LLM 模型,几乎清一色都是类似 GPT 3.0 这种「自回归语言模型 + Prompting」模式的,比如 GPT-3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA 等,没有例外。为什么会这样呢?背后一定有其必然性,我认为可能主要源于两个原因。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-2.jpeg\" alt=\"image\" /></p>\n\n<p>首先,Google 的 T5 模型,在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示,标为红色的是个文本分类问题,黄色的是判断句子相似性的回归或分类问题,这都是典型的自然语言理解问题。在 T5 模型里,这些自然语言理解问题在输入输出形式上和生成问题保持了一致,也就是说,可以把分类问题转换成让 LLM 模型生成对应类别的字符串,这样理解和生成任务在表现形式就实现了完全的统一。</p>\n\n<p>这说明自然语言生成任务,在表现形式上可以兼容自然语言理解任务,若反过来,则很难做到这一点。这样的好处是:同一个 LLM 生成模型,可以解决几乎所有 NLP 问题。而如果仍然采取 Bert 模式,则这个 LLM 模型无法很好处理生成任务。既然这样,我们当然倾向于使用生成模型,这是一个原因。</p>\n\n<p>第二个原因,如果想要以零示例提示语(zero shot prompting)或少数示例提示语(few shot prompting)的方式做好任务,则必须要采取 GPT 模式。现在已有研究(参考:<a href=\"https://arxiv.org/pdf/2205.11726\">《On the Role of Bidirectionality in Language Model Pre-Training》</a>)证明:如果是以 fine-tuning 方式解决下游任务,Bert模式的效果优于 GPT 模式;若是以 zero shot / few shot prompting 这种模式解决下游任务,则GPT模式效果要优于 Bert 模式。这说明了,生成模型更容易做好 zero shot/few shot prompting 方式的任务,而Bert模式以这种方式做任务,是天然有劣势的。这是第二个原因。</p>\n\n<p>但是问题来了:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?要解释清楚这个问题,我们首先需要搞清楚另外一个问题:什么样的 LLM 模型,对我们是最理想的?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-3.jpeg\" alt=\"image\" /></p>\n\n<p>上图展示了一个理想的 LLM 该有的样子。首先,LLM 应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它,它应该能够自动从中学习到里面包含的所有知识点,学习过程不需要人的介入,并且能灵活应用所学知识,来解决实际问题。因为数据是海量的,要吸收所有知识,就要非常多的模型参数来存储知识,所以这个模型必然会是一个巨无霸模型。</p>\n\n<p>其次,LLM 应该能解决 NLP 任何子领域的问题,而不仅支持有限领域,甚至它应该可以响应 NLP 之外其它领域的问题,最好是任意领域的问题都能得到很好地回答。</p>\n\n<p>再者,当我们使用 LLM 解决某个具体领域问题的时候,应该用我们人类习惯的表达方式,就是说LLM应该理解人类的命令。这体现出让 LLM 适配人,而不是反过来,让人去适配 LLM 模型。人适配 LLM 的典型例子,比如绞尽脑汁去尝试各种不同的 prompt,以试图找到好的提示语,才能很好地解决手头问题。关于这点,上图在人类和 LLM 交互的接口层,举了几个例子,说明什么是好的人使用 LLM 模型的接口形式。</p>\n\n<p>看完这个理想中的 LLM,我们再回头解释上面遗留的问题:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?有两个原因。</p>\n\n<p>第一,这个 LLM 模型规模必然非常巨大,有能力作出这个模型,或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人,就算你把模型开源出来,他们也无力部署这个模型,更不用说再用 Fine-tuning 这种模式去修改模型参数了。所以,我们应该追求不修正模型参数,就能让任务需求方完成任务的方式,也就是应该采取 prompt 模式完成任务,而非 Fine-tuning 模式(由此可看出,soft prompting 技术方向是违背这个发展趋势的)。模型制作方则将 LLM 作成公用服务,以 LLM as Service 的模式运行。作为服务支持方,考虑到千变万化的用户需求,所以 LLM 模型制作方更要追求让 LLM 能完成尽可能多类型的任务,这是附带的影响,也是为何超级大模型一定会追求走向AGI的现实因素。</p>\n\n<p>第二,zero shot prompting 也好,few shot prompting 也好,甚至促进LLM推理能力的思维链(CoT,Chain of Thought)Prompting 也好,就是上图中接口层中的现有技术。具体而言,zero shot prompting 的初衷,其实就是人类和 LLM 的理想接口,直接用人类所习惯的任务表述方式让 LLM 做事情,但是发现 LLM 并不能很好地理解,效果也不好。经过继续研究,转而发现:对于某项任务,如果给 LLM 几个示例,用这些示例来代表任务描述,效果会比 zero shot prompting 好,于是大家都去研究更好的 few shot prompting 技术。可以理解为,本来我们希望 LLM 能够用人类常用的命令方式来执行某个任务,但是目前技术还做不到,所以退而求其次,用这些替代技术来表达人类的任务需求。</p>\n\n<p>如果理解了上述逻辑,很容易得出如下结论:few shot prompting(也被称为In Context Learning)只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务,而且 LLM 可以理解,那么,我们肯定会毫不犹豫地抛弃这些过渡期的技术,原因很明显,用这些方法来描述任务需求,并不符合人类的使用习惯。</p>\n\n<p>这也是为何我将 GPT 3.0 + Prompting 列为过渡期技术的原因,ChatGPT 的出现,改变了这个现状,用 Instruct 取代了 Prompting,由此带来新的技术范式转换,并产生若干后续影响。</p>\n\n<h3 id=\"影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</h3>\n\n<p>在理想 LLM 的背景下,我们再来看 ChatGPT,能更好理解它的技术贡献。ChatGPT 应该是目前所有的现有技术里,最接近理想 LLM 的技术方法。如果归纳下 ChatGPT 最突出特点的话,我会用下面八个字「能力强大,善解人意」。</p>\n\n<p>「能力强大」这一点,我相信应该主要归功于 ChatGPT 所依托的基础 LLM GPT-3.5。因为 ChatGPT 尽管加入了人工标注数据,但是量级只有数万,这个规模的数据量,和训练 GPT 3.5 模型使用的几千亿 token 级别的数据量相比,包含的世界知识(数据中包含的事实与常识)可谓沧海一粟,几可忽略,基本不会对增强 GPT 3.5 的基础能力发挥什么作用。所以它的强大功能,应该主要来自于隐藏在背后的 GPT 3.5。GPT 3.5 对标理想 LLM 模型中的那个巨无霸模型。</p>\n\n<p>那么,ChatGPT 向 GPT 3.5 模型注入新知识了吗?应该是注入了,这些知识就包含在几万人工标注数据里,不过注入的不是世界知识,而是人类偏好知识。所谓「人类偏好」,包含几方面的含义:首先,是人类表达一个任务的习惯说法。比如,人习惯说「把下面句子从中文翻译成英文」,以此表达一个「机器翻译」的需求,但是 LLM 又不是人,它怎么会理解这句话到底是什么意思呢?你得想办法让 LLM 理解这句命令的含义,并正确执行。所以,ChatGPT 通过人工标注数据,向GPT 3.5 注入了这类知识,方便 LLM 理解人的命令,这是它“善解人意”的关键。其次,对于什么是好的回答,什么是不好的回答,人类有自己的标准,例如比较详细的回答是好的,带有歧视内容的回答是不好的,诸如此类。这是人类自身对回答质量好坏的偏好。人通过 Reward Model 反馈给 LLM 的数据里,包含这类信息。总体而言,ChatGPT 把人类偏好知识注入 GPT 3.5,以此来获得一个听得懂人话、也比较礼貌的 LLM。</p>\n\n<p>可以看出,ChatGPT 的最大贡献在于:基本实现了理想 LLM 的接口层,让 LLM 适配人的习惯命令表达方式,而不是反过来让人去适配 LLM,绞尽脑汁地想出一个能 Work 的命令(这就是 instruct 技术出来之前,prompt 技术在做的事情),而这增加了 LLM 的易用性和用户体验。是 InstructGPT / ChatGPT 首先意识到这个问题,并给出了很好的解决方案,这也是它最大的技术贡献。相对之前的 few shot prompting,它是一种更符合人类表达习惯的人和 LLM 进行交互的人机接口技术。</p>\n\n<p>而这必将启发后续的 LLM 模型,继续在易用人机接口方面做进一步的工作,让 LLM 更听话。</p>\n\n<h3 id=\"影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</h3>\n\n<p>就 NLP 领域而言,这次范式转换,意味着很多目前独立存在的 NLP 研究领域,将被纳入 LLM 的技术体系,进而不再独立存在,逐步消失。经过第一次范式转换,尽管 NLP 中很多「中间任务」,继续作为独立研究领域存在不再必要,但是大多数「最终任务」,仍然是以独立研究领域存在的,只是切换成在「预训练 + fine-tuning」框架下,面对领域独有问题,陆续提出新的改进方案。</p>\n\n<p>目前研究表明,很多 NLP 任务,随着 LLM 模型规模增长,效果会大幅提升。据此,我觉得可得到如下推论:大多数某领域所谓「独有」的问题,大概率只是缺乏领域知识导致的一种外在表象,只要领域知识足够多,这个所谓领域独有的问题,就可以被很好地解决掉,其实并不需要专门针对某个具体领域问题,冥思苦想去提出专用解决方案。也许 AGI 的真相超乎意料地简单:你只要把这个领域更多的数据交给 LLM,让它自己学习更多知识即可。</p>\n\n<p>在这个背景下,同时,ChatGPT 证明了我们现在是可以直接去追求理想 LLM 模型的,那么,未来的技术发展趋势应该是:追求规模越来越大的 LLM 模型,通过增加预训练数据的多样性,来涵盖越来越多的领域,LLM 自主从领域数据中通过预训练过程学习领域知识,随着模型规模不断增大,很多问题随之得到解决。研究重心会投入到如何构建这个理想 LLM 模型,而非去解决某个领域的具体问题。这样,越来越多 NLP 的子领域会被纳入 LLM 的技术体系,进而逐步消失。</p>\n\n<p>我认为,判断某个具体领域是否该立即停止独立研究,其判断标准可采取以下两种方法,占其一即可:第一,判断某个任务,是否 LLM 的研究效果超过人类表现,对于那些 LLM 效果超过人类的研究领域,已无独立研究的必要。举个例子,GLUE 与 SuperGLUE 测试集合里的很多任务,目前 LLM 效果已超过人类表现,与这个数据集合密切相关的研究领域,其实就没有继续独立存在的必要。第二,对比两种模式的任务效果,第一种模式是用较大的领域专用数据进行 Fine-tuning,第二种是 few-shot prompting 或 instruct-based 方法。如果第二种方法效果达到或超过第一种方法,则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看,其实很多研究领域,目前 fine-tuning 效果还是占优的(因为这种模式领域训练数据量大),看似还可独立存在。但是考虑到很多任务随着模型规模增大,few shot prompting 效果持续增长,随着更大模型的出现,这个拐点很可能短期就会达到。</p>\n\n<p>如果上述猜测成立,将意味着如下残酷事实:对于很多 NLP 领域的研究人员,将面临往何处去的选择,是继续做领域独有问题呢?还是放弃这种看似前途不大的方式,转而去建设更好的LLM?如果选择转向去建设 LLM,又有哪些机构有能力、有条件去做这个事情呢?你对这个问题的回答会是什么呢?</p>\n\n<h3 id=\"影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</h3>\n\n<p>如果站在 AGI 的视角,参照之前描述的理想 LLM 模型,它所能完成的任务,不应局限于 NLP 领域,或某一两个学科领域,理想中的 LLM 应该是领域无关的通用人工智能模型,它现在在某一两个领域做得好,不代表只能做这些任务。ChatGPT 的出现,证明了现在这个时期,我们去追求AGI是有可行性的,而现在是抛开「领域学科」这个思维束缚的时候了。</p>\n\n<p>ChatGPT 除了展示出以流畅的对话形式解决各种 NLP 任务外,也具备强大的代码能力。很自然的,之后越来越多其它的研究领域,也会被逐步纳入 LLM 体系中,成为通用人工智能的一部分。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-4.jpeg\" alt=\"image\" /></p>\n\n<p>LLM 从 NLP 向外进行领域拓展,一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入,让LLM成为一个支持多模态输入输出的通用人机接口,典型的例子包括 DeepMind 的 Flamingo 和微软的<a href=\"https://arxiv.org/pdf/2206.06336.pdf\">《Language Models are General-Purpose Interfaces》</a>,上图展示了这种方式的概念结构。</p>\n\n<p>我的判断是无论是图像还是多模态,未来被融入 LLM 成为好用的功能,可能比我们想象的进度要慢。主要原因在于:尽管图像领域最近两年也一直在模仿 Bert 预训练的路子,尝试引入自监督学习,释放模型自主从图像数据中学习知识的能力,典型技术就是“对比学习”和 MAE,这是两条不同的技术路线。然而,从目前效果来看,尽管取得了很大的技术进步,但貌似这条路尚未走通,这体现在图像领域预训练模型应用到下游任务,带来的效果收益,远不如 Bert 或 GPT 应用在 NLP 下游任务那样显著。所以,图像预处理模型仍需深入探索,以释放图像数据的潜力,而这会迟滞它们被统一到 LLM 大模型的时间。当然,如果哪天这条路被趟通,大概率会复现NLP领域目前的局面,就是图像处理各个研究子领域可能会逐步消失,被融入到大型 LLM 中来,直接完成终端任务。</p>\n\n<p>除了图像与多模态,很明显,其它领域也会逐渐被纳入到理想 LLM 中来,这个方向方兴未艾,是具备高价值的研究主题。</p>\n\n<p>以上是我对范式转换的个人思考,接下来,我们来梳理下 GPT 3.0 之后 LLM 模型的主流技术进展。如理想 LLM 模型所示,相关的技术其实可以分为两大类;一类是关于 LLM 模型如何从数据中吸收知识,也包括模型规模增长对 LLM 吸收知识能力带来的影响;第二类是关于人如何使用 LLM 内在能力来解决任务的人机接口,包括In Context Learning 和 Instruct 两种模式。思维链(CoT)prompting 这种 LLM 推理技术,本质上也属于 In Context Learning,因为比较重要,我就把它们单独拎出来讲一下。</p>\n\n<h2 id=\"二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</h2>\n\n<p>从目前研究结果看,Transformer 是足够强大的特征抽取器,尚不需要做特别的改进。那么通过预训练过程,Transformer 学到了什么?知识是如何存取的?我们又如何修正错误知识?本节讲述这方面的研究进展。</p>\n\n<h3 id=\"1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</h3>\n\n<p>LLM 从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,可以分为语言类知识和世界知识两大类。</p>\n\n<p>语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于 LLM 能否捕获语言知识有较长研究历史,自从 Bert 出现以来就不断有相关研究,很早就有结论,各种实验充分证明 LLM 可以学习各种层次类型的语言学知识,这也是为何使用预训练模型后,各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外,各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在 Transformer 的低层和中层,而抽象的语言知识比如语义类知识,广泛分布在 Transformer 的中层和高层结构中。</p>\n\n<p>世界知识指的是在这个世界上发生的一些真实事件(事实型知识,Factual Knowledge),以及一些常识性知识(Common Sense Knowledge)。比如「拜登是现任美国总统」、「拜登是美国人」、「乌克兰总统泽连斯基与美国总统拜登举行会晤」,这些都是和拜登相关的事实类知识;而「人有两只眼睛」、「太阳从东方升起」这些属于常识性知识。关于 LLM 模型能否学习世界知识的研究也有很多,结论也比较一致:LLM 确实从训练数据中吸收了大量世界知识,而这类知识主要分布在 Transformer 的中层和高层,尤其聚集在中层。而且,随着 Transformer 模型层深增加,能够学习到的知识数量逐渐以指数级增加(可参考<a href=\"https://arxiv.org/pdf/2106.02902.pdf\">《BERTnesia: Investigating the capture and forgetting of knowledge in BERT》</a>)。其实,你把 LLM 看作是一种以模型参数体现的隐式知识图谱,如果这么理解,我认为是一点问题也没有的。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2011.04946\">《When Do You Need Billions of Words of Pre-training Data?》</a>这篇文章研究了预训练模型学习到的知识量与训练数据量的关系,它的结论是:对于 Bert 类型的语言模型来说,只用 1000 万到 1 亿单词的语料,就能学好句法语义等语言学知识,但是要学习事实类知识,则要更多的训练数据。这个结论其实也是在意料中的,毕竟语言学知识相对有限且静态,而事实类知识则数量巨大,且处于不断变化过程中。而目前研究证明了随着增加训练数据量,预训练模型在各种下游任务中效果越好,这说明了从增量的训练数据中学到的更主要是世界知识。</p>\n\n<h3 id=\"2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</h3>\n\n<p>由上可知,LLM 确实从数据中学到了很多语言类及世界知识。那么,对于某条具体的知识,LLM 把它存储到了哪里?又是如何提取出来的?这也是一个有意思的问题。</p>\n\n<p>显然,知识一定存储在 Transformer 的模型参数里。从 Transformer 的结构看,模型参数由两部分构成:<strong>多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中</strong>。MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点,那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-5.jpeg\" alt=\"image\" /></p>\n\n<p>但这样的定位,粒度还是太粗,无法很好回答具体某条知识是如何存储与提取的,比如「中国的首都是北京」这条知识,以三元组表达就是<北京,is-capital-of,中国>,其中「is-capital-of」代表实体间关系。<strong>这条知识它存储在 LLM 的哪里呢?</strong></p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:这些知识存哪了?我们现在有以下几点认知:<br />\n1、多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中。<br />\n2、MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点。<br />\n3、那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。<br />\n4、一些研究达成共识:Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,比如《Transformer Feed-Forward Layers Are Key-Value Memories》</p>\n</blockquote>\n\n<p><a href=\"https://arxiv.org/pdf/2012.14913.pdf\">《Transformer Feed-Forward Layers Are Key-Value Memories》</a>给出了一个比较新颖的观察视角,它把 Transformer 的 FFN 看成存储大量具体知识的 Key-Value 存储器。如上图所示(图左是原始论文图,其实不太好理解,可以看做了注释的图右,更好理解些),FFN 的第一层是个 MLP 宽隐层,这是 Key 层;第二层是 MLP 窄隐层,是 Value 层。FFN 的输入层其实是某个单词对应的 MHA 的输出结果Embedding,也就是通过 Self Attention,将整个句子有关的输入上下文集成到一起的 Embedding,代表了整个输入句子的整体信息。</p>\n\n<p>Key 层的每个神经元节点,记载了一对信息。比如对于上图中 FFN 第一个隐层的第 i 个节点 ki,也许就是它记载了 <北京,is-capital-of,中国> 这条知识。ki 节点对应的 key 向量,其实指的是节点 ki 和输入层每个节点的权重向量;而对应的 Value 向量,指的是节点 ki 和 FFN 第二层的 Value 层每个节点形成连接的权重向量。每个神经元的 Key 向量,用于识别输入中的某种语言或者知识模式,是一种模式探测器。如果输入中包含它要检测的某种模式,那么输入向量和 ki 节点的 key 权重进行向量内积计算,加上 Relu,形成 ki 的大数值响应,意味着 ki 检测到了这个模式,于是再把这个响应值,通过 ki 节点的 Value 权重向量向 FFN 第二层传播。这等价于将 Value 向量的值,用响应值加权,然后传递并体现到第二层 Value 层每个节点的输出上。如此这般,FFN 的正向传播计算过程,看起来就像是通过 Key 检测到某种知识模式,然后取出对应的 Value,并把 Value 体现在FFN的第二层输出上。当然,FFN 第二层每个节点,会收集 FFN 的 Key 层所有节点信息,所以是一种混合响应,而 Value 层所有节点的混合响应,可以解读为代表输出单词的概率分布信息。</p>\n\n<p>听着可能还是比较复杂,我们用个极端的例子来说明。我们假设上图的节点 ki就是记载 <北京,is-capital-of,中国>这条知识的 Key-Value 存储器,它的 Key 向量,用于检测「中国的首都是…」这个知识模式,它的 Value 向量,基本存储了与单词「北京」的 Embedding 比较接近的向量。当 Transformer 的输入是「中国的首都是[Mask]」的时候,ki 节点从输入层探测到这个知识模式,所以产生较大的响应输出。我们假设 Key 层其它神经元对这个输入都没有任何响应,那么对应的Value层的节点,其实只会接收到「北京」这个 Value 对应的单词 embedding,并通过 ki的大响应值,进行了进一步的数值放大。于是,Mask 位置对应的输出,就自然会输出「北京」这个单词。基本就是这么个过程,看着很复杂,其实很简单。</p>\n\n<p>而且这篇文章还指出,Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,就是说低层FFN存储词法、句法等表层知识,中层和高层存储语义及事实概念知识,这和其它研究结论是一致的。</p>\n\n<p><strong>要我猜,把 FFN 看成 Key-Value 存储器这种思路,很可能不是最终的正确答案,但是距离最终正确答案的距离,估计也不太远</strong>。</p>\n\n<h3 id=\"3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</h3>\n\n<p>既然我们已知具体的某条世界知识存储在某个或者某些 FFN 节点的参数里,自然会引发另外一个问题:我们能否修正 LLM 模型里存储的错误或者过时的知识呢?比如对于问题「英国的现任首相是谁?」鉴于近年来英国首相频繁更迭,你猜 LLM 更倾向输出「鲍里斯」还是更青睐「苏纳克」?很明显训练数据中包含“鲍里斯”的数据会更多,这种情况很大可能 LLM 会给出错误回答,于是我们就有修正 LLM 里存储的过时知识的必要性。</p>\n\n<p>如果归纳下,目前有三类不同方法来修正 LLM 里蕴含的知识:</p>\n\n<p>第一类方法从训练数据的源头来修正知识。<a href=\"https://arxiv.org/pdf/2205.11482.pdf\">《Towards Tracing Factual Knowledge in Language Models Back to the Training Data》</a>这篇文章的研究目标是:对于指定的某条知识,我们是否可以定位到是哪些训练数据导致 LLM 学会了这条知识?答案是肯定的,这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术,假设我们想要删除某条知识,则可首先定位到其对应的数据源头,删除数据源,然后重新预训练整个 LLM 模型,这样即可达成删除 LLM 中相关知识的目的。但是这里有个问题,如果修正一小部分知识,我们就需要重新做一次模型预训练,这样做明显成本太高。所以这种方法不会太有发展前景,可能比较适合那种对于某个特定类别数据的一次性大规模删除场合,不适合少量多次的常规知识修正场景,比如可能比较适合用来做去除偏见等去 toxic 内容的处理。</p>\n\n<p>第二类方法是对 LLM 模型做一次 fine-tuning 来修正知识。一个直观能想到的方法是:我们可以根据要修正成的新知识来构建训练数据,然后让 LLM 模型在这个训练数据上做 fine-tuning,这样指导 LLM 记住新的知识,遗忘旧的知识。这个方法简单直观,但是也有一些问题,首先它会带来灾难遗忘问题,就是说除了忘掉该忘的知识,还忘掉了不该忘的知识,导致这么做了之后有些下游任务效果下降。另外,因为目前的 LLM 模型规模非常大,即使是做 fine-tuning,如果次数频繁,其实成本也相当高。对这种方法感兴趣的可以参考<a href=\"https://arxiv.org/pdf/2012.00363.pdf\">《Modifying Memories in Transformer Models》</a>。</p>\n\n<p>另外一类方法直接修改 LLM 里某些知识对应的模型参数来修正知识。假设我们想要把旧知识 <英国,现任首相,鲍里斯>,修正到 <英国,现任首相,苏纳克>。首先我们想办法在 LLM 模型参数中,定位到存储旧知识的 FFN 节点,然后可以强行调整更改 FFN 中对应的模型参数,将旧知识替换成新的知识。可以看出,这种方法涉及到两项关键技术:首先是如何在 LLM 参数空间中定位某条知识的具体存储位置;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。理解这个修正 LLM 知识的过程,其实对于更深入理解 LLM 的内部运作机制是很有帮助的。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:如何修改已存储的知识?<br />\n首先是如何定位存哪了;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。</p>\n</blockquote>\n\n<h2 id=\"三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</h2>\n\n<p>我们知道,近年来,LLM 模型规模在快速增长,目前效果最好的 LLM 模型,其参数规模大都超过了千亿(100B)参数规模。比如,OpenAI 的 GPT 3 的规模为 175B,Google 的 LaMDA 规模为 137B,PaLM 的规模为 540B,DeepMind 的 Gogher 规模为 280B 等,不一而足。国内也有中文巨型模型,比如智源 GLM 规模 130B,华为「盘古」规模 200B,百度「文心」规模 260B,浪潮「源1.0」规模 245B。那么,一个很自然的问题就是:随着 LLM 模型规模不断增长,会发生些什么呢?</p>\n\n<p>预训练模型的应用往往是两阶段的:预训练阶段,及具体场景应用阶段。在预训练阶段,其优化目标是交叉熵,对 GPT 这种自回归语言模型来说,也就是看 LLM 是否正确预测到了下一个单词;而场景应用阶段,一般要看具体场景的评价指标。一般我们的直觉是:如果 LLM 模型在预训练阶段的指标越好,自然它解决下游任务的能力就越强。然而,事实并非完全如此。现有研究已证明,预训练阶段的优化指标确实和下游任务表现出正相关关系,但是并非完全正相关。也就是说,只看预训练阶段的指标,来判断一个 LLM 模型是否够好,这是不够的。基于此,我们分头来看在这两个不同阶段,随着 LLM 模型增大,有什么影响。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-6.jpeg\" alt=\"image\" /></p>\n\n<p>首先,我们先看在预训练阶段,随着模型规模逐步增大,会发生什么。OpenAI 在<a href=\"https://arxiv.org/pdf/2001.08361\">《Scaling Laws for Neural Language Models》</a>中专门研究了这个问题,并提出 LLM 模型所遵循的「伸缩法则(scaling law)」。如上图所示,这个研究证明:<strong>当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好</strong>。</p>\n\n<p>既然三个因素都重要,那么我们在实际做预训练的时候,就有一个算力如何分配的决策问题:假设用于训练 LLM 的算力总预算(比如多少 GPU 小时或者 GPU 天)给定,那么是应该多增加数据量、减少模型参数呢?还是说数据量和模型规模同时增加,减少训练步数呢?此消彼长,某个要素规模增长,就要降低其它因素的规模,以维持总算力不变,所以这里有各种可能的算力分配方案。最终 OpenAI 选择了同时增加训练数据量和模型参数,但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了:对于训练数据量和模型参数这两个要素,如果只单独增加其中某一个,这不是最好的选择,最好能按照一定比例同时增加两者,它的结论是优先增加模型参数,然后才是训练数据量。假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 5.5 倍的模型参数量,1.8 倍的训练数据量,此时模型效果最佳。</p>\n\n<p>DeepMind 的一项研究(参考<a href=\"https://arxiv.org/pdf/2203.15556\">《Training Compute-Optimal Large Language Models》</a>)更深入地探究了这个问题,其基本结论和 OpenAI 的结论差不多,比如确实需要同时增加训练数据量和模型参数,模型效果才会更好。而很多大模型在做预训练的时候,并没有考虑这一点,很多 LLM 大模型只是单调增加模型参数,而固定住了训练数据量,这个做法其实是不对的,限制了 LLM 模型的潜力。但是它修正了两者的比例关系,<strong>认为训练数据量和模型参数是同等重要的,也就是说,假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 3.3 倍的模型参数量,3.3 倍的训练数据量,这样模型效果才最好</strong>。</p>\n\n<p>这意味着:增加训练数据量的重要性,比我们之前所认为的,还要重要。基于这个认知,DeepMind 在设计 Chinchilla 模型时,在算力分配上选择了另外一种配置:对标数据量 300B、模型参数量 280B 的 Gopher 模型,Chinchilla 选择增加 4 倍的训练数据,但是将模型参数降低为 Gopher 的四分之一,大约为70B。但是无论预训练指标,还是很多下游任务指标,Chinchilla 效果都要优于规模更大的 Gopher。</p>\n\n<p>这带给我们如下启示:我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。缩小模型规模有很多好处,比如在应用的时候,推理速度会快很多等,无疑这是一个很有前途的 LLM 发展路线。</p>\n\n<p>以上是从预训练阶段来看模型规模的影响,如果从 LLM 解决下游具体任务效果的角度来看,随着模型规模增大,不同类型的任务有不同的表现,具体而言,有以下三类情况。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-7.jpeg\" alt=\"image\" /></p>\n\n<p>第一类任务完美体现了 LLM 模型的 scaling law,就是说随着模型规模逐步放大,任务的表现越来越好,如上图里的(a)图所示。这类任务通常符合如下共性:它们往往都是知识密集型任务,也就是说如果 LLM 模型包含的知识量越多,这类任务表现越好。而很多研究已经证明越大的 LLM 模型学习效率越高,也就是说相同训练数据量,模型越大任务效果越好,说明面对的即使是同样的一批训练数据,更大的 LLM 模型相对规模小一些的模型,从中学到了更多的知识。更何况一般情况下,在增大 LLM 模型参数的时候,往往会同步增加训练数据量,这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图,为何随着模型规模增大,这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务,其实都属于这种知识密集型任务,而很多任务在近两年获得了极大的效果提升,甚至超过了人类表现。很明显,这大概率是 LLM 模型的规模增长带来的,而非归功于某项具体的技术改进。</p>\n\n<p>第二类任务展现出 LLM 具备某种「<strong>涌现能力(Emergent Ability)</strong>」,如上图(b)所示。所谓「涌现能力」,指的是当模型参数规模未能达到某个阀值时,模型基本不具备解决此类任务的任何能力,体现为其性能和随机选择答案效果相当,但是当模型规模跨过阀值,LLM 模型对此类任务的效果就出现突然的性能增长。也就是说,模型规模是解锁(unlock)LLM 新能力的关键,随着模型规模越来越大,会逐渐解锁 LLM 越来越多的新能力。这是个很神奇的现象,因为它意味着如下让人对未来可报乐观预期的可能:或许很多任务,目前 LLM 还不能很好地解决,甚至站在现在这个时刻的我们看起来,LLM 完全没有能力解决这类任务,但因 LLM 具备「涌现能力」,所以如果我们继续推大模型,也许某一天它的这项能力就被突然解锁了。LLM 模型的规模增长会给我们带来意想不到的精彩礼物。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2206.04615\">《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》</a>这篇文章指出,这类<strong>体现出「涌现能力」的任务也有一些共性:这些任务一般由多步骤构成,要解决这些任务,往往需要先解决多个中间步骤,而逻辑推理能力在最终解决这类任务中发挥重要作用</strong>。思维链(Chain of Thought)Prompting 是典型的增强 LLM 推理能力的技术,能大幅提升此类任务的效果,关于 CoT 技术,在随后小节内容会做解释,此处暂不展开。</p>\n\n<p>问题是,为何 LLM 会出现这种「涌现能力」现象呢?上述文章以及<a href=\"https://arxiv.org/pdf/2206.07682\">《Emergent Abilities of Large Language Models》</a>给出了几个可能的解释:</p>\n\n<p>一种可能解释是<strong>有些任务的评价指标不够平滑</strong>。比如说有些生成任务的判断标准,它要求模型输出的字符串,要和标准答案完全匹配才算对,否则就是 0 分。所以,即使随着模型增大,其效果在逐步变好,体现为输出了更多的正确字符片段,但是因为没有完全对,只要有任何小错误都给 0 分,只有当模型足够大,输出片段全部正确才能得分。也就是说,因为指标不够平滑,所以不能体现 LLM 其实正在逐步改善任务效果这一现实,看起来就是「涌现能力」这种外在表现。</p>\n\n<p>另外一种可能的解释是:有些任务由若干中间步骤构成,随着模型规模增大,解决每个步骤的能力也在逐步增强,但是只要有一个中间步骤是错的,最终答案就是错的,于是也会导致这种表面的「涌现能力」现象。</p>\n\n<p>当然,<strong>上面的解释目前还都是猜想,至于为何 LLM 会出现这种现象,还需要进一步更深入的研究</strong>。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-8.jpeg\" alt=\"image\" /></p>\n\n<p>还有少部分任务,随着模型规模增长,任务的效果曲线展现出 U 形特性:随着模型规模逐渐变大,任务效果逐渐变差,但是当模型规模进一步增长,则效果开始越来越好,呈现出 U 形增长趋势,如上图所示的粉红色 PaLM 模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢?<a href=\"https://arxiv.org/pdf/2211.02011\">《Inverse scaling can become U-shaped》</a>这篇文章给出了一种解释:这些任务,内部其实隐含了两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。当模型规模小的时候,无法识别任意一种子任务,所以模型的表现跟随机选择答案差不多,当模型增长到中等规模的时候,主要执行的是干扰任务,所以对真正的任务效果有负面影响,体现为真正任务效果的下降,而当进一步增加模型规模,则 LLM 可以忽略干扰任务,执行真正的任务,体现为效果开始增长。</p>\n\n<p>对于那些随着模型规模增大,效果一直下降的任务,如果采用思维链(CoT)Prompting,则部分任务的表现转换为遵循 Scaling law,即模型规模越大效果越好,而其它任务则转换为U性增长曲线。这其实侧面说明了:此类任务应属于推理类型的任务,所以加入 CoT 后任务表现会发生质的变化。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:训练数据规模、模型参数规模和训练时长(步数),与最终 LLM 性能(loss 衡量)之间什么关系?<br />\n1、当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好。<br />\n2、训练数据量和模型参数是同等重要的。<br />\n3、我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么有些模型不遵循 scaling law?三类任务:<br />\n第一类完美遵循 scaling low。<br />\n第二类过了阈值后涌现。《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》和《Emergent Abilities of Large Language Models》认为是指标不平滑 or 中间步骤是错的。\n第三类 U 型,《Inverse scaling can become U-shaped》猜测可能两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。</p>\n</blockquote>\n\n<h2 id=\"四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</h2>\n\n<p>一般我们经常提到的人和 LLM 的接口技术包括:zero shot prompting、few shot prompting、In Context Learning,以及 Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献,会发现叫法比较乱。</p>\n\n<p>其中 Instruct 是 ChatGPT 的接口方式,就是说人以自然语言给出任务的描述,比如「把这个句子从中文翻译成英文」,类似这种。zero shot prompting 我理解其实就是现在的 Instruct 的早期叫法,以前大家习惯叫 zero shot,现在很多改成叫 Instruct。尽管是一个内涵,但是具体做法是两种做法。早期大家做 zero shot prompting,实际上就是不知道怎么表达一个任务才好,于是就换不同的单词或者句子,反复在尝试好的任务表达方式,这种做法目前已经被证明是在拟合训练数据的分布,其实没啥意思。目前 Instruct 的做法则是给定命令表述语句,试图让 LLM 理解它。所以尽管表面都是任务的表述,但是思路是不同的。</p>\n\n<p>而In Context Learning 和 few shot prompting 意思类似,就是给 LLM 几个示例作为范本,然后让LLM解决新问题。我个人认为 In Context Learning 也可以理解为某项任务的描述,只是 Instruct 是一种抽象的描述方式,In Context Learning 是一种例子示范的例子说明法。当然,鉴于目前这几个叫法用的有点乱,所以上述理解仅代表个人看法。</p>\n\n<p>所以我们此处只对 In Context Learning 和 Instruct 进行介绍,不再提 zero shot 和 few shot 了。</p>\n\n<h3 id=\"1神秘的-in-context-learning\">1、神秘的 In Context Learning</h3>\n\n<p>如果你细想,会发现 In Context Learning 是个很神奇的技术。它神奇在哪里呢?神奇在你提供给 LLM 几个样本示例,….,然后给它 x(n+1),LLM 竟然能够成功预测对应的 y(n+1)。听到这你会反问:这有什么神奇的呢?Fine-tuning 不就是这样工作的吗?你要这么问的话,说明你对这个问题想得还不够深入。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-9.jpeg\" alt=\"image\" /></p>\n\n<p>Fine-tuning 和 In Context Learning 表面看似都提供了一些例子给 LLM,但两者有质的不同(参考上图示意):Fine-tuning 拿这些例子当作训练数据,利用反向传播去修正 LLM 的模型参数,而修正模型参数这个动作,确实体现了 LLM 从这些例子学习的过程。但是,In Context Learning 只是拿出例子让 LLM 看了一眼,并没有根据例子,用反向传播去修正 LLM 模型参数的动作,就要求它去预测新例子。既然没有修正模型参数,这意味着貌似 LLM 并未经历一个学习过程,如果没有经历学习过程,那它为何能够做到仅看一眼,就能预测对新例子呢?这正是 In Context Learning 的神奇之处。这是否让你想起了一句歌词「只是因为在人群中多看了你一眼 再也没能忘掉你容颜」,而这首歌名叫「传奇」。你说传奇不传奇?</p>\n\n<p>看似 In Context Learning 没从例子里学习知识,实际上,难道 LLM 通过一种奇怪的方式去学习?还是说,它确实也没学啥?关于这个问题的答案,目前仍是未解之谜。现有一些研究各有各的说法,五花八门,很难判断哪个讲述的是事实的真相,甚至有些研究结论还相互矛盾。这里提供几个目前的说法,至于谁对谁错,只能你自己把握了。当然,我认为追求这个神奇现象背后的真相,是一个好的研究课题。</p>\n\n<p>试图证明 In Context Learning 没有从例子中学习的工作是<a href=\"https://arxiv.org/pdf/2202.12837\">《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》</a>。它发现了:在提供给 LLM 的样本示例中,yi 是否 xi 对应的正确答案,其实并不重要,如果我们把正确答案 yi 替换成随机的另外一个答案 yj ,这并不影响 In Context Learning 的效果。这起码说明了一点:In Context Learning 并没有提供给 LLM 那个从 x 映射到 y 的映射函数信息:y=f(x),否则的话你乱换正确标签,肯定会扰乱这个 y=f(x) 映射函数。也就是说,In Context Learning 并未学习这个输入空间到输出空间的映射过程。</p>\n\n<p>真正对 In Context Learning 影响比较大的是:x 和 y 的分布,也就是输入文本 x 的分布和候选答案 y 有哪些,如果你改变这两个分布,比如把 y 替换成候选答案之外的内容,则 In Context Learning 效果急剧下降。</p>\n\n<p>总之,这个工作证明了 In Context Learning 并未学习映射函数,但是输入和输出的分布很重要,这两个不能乱改。</p>\n\n<p>有些工作认为 LLM 还是从给出的示例学习了这个映射函数 y=f(x),不过是种隐式地学习。比如<a href=\"https://arxiv.org/pdf/2211.15661.pdf\">《What learning algorithm is in-context learning? Investigations with linear models》</a>认为 Transformer 能够隐式地从示例中学习 x 到 y 的映射过程,它的激活函数中包含了一些简单映射函数,而 LLM 通过示例能够激发对应的那一个。而<a href=\"https://arxiv.org/pdf/2212.10559\">《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》</a>这篇文章则将 ICL 看作是一种隐式的 Fine-tuning。</p>\n\n<p>总而言之,<strong>目前这还是一个未解之谜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】LLM 技术增量重点</strong>:In Context Learning</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:为什么 In Context Learning 有效?<br />\n1、《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》认为 ICL 没有从例子里学习 x 到 y 的映射关系,而只是学习了分布与分布的对应。<br />\n2、《What learning algorithm is in-context learning? Investigations with linear models》认为 ICL 隐式地学习了 x 到 y 的映射关系。<br />\n3、《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》认为 ICL 是隐式的 fine-tuning。</p>\n</blockquote>\n\n<h3 id=\"2神奇的-instruct-理解\">2、神奇的 Instruct 理解</h3>\n\n<p>我们可以把 Instruct 当作一种方便人类理解的任务表述,在这个前提下,目前关于 Instruct 的研究可以分成两种:偏学术研究的 Instruct,以及关于人类真实需求描述的 Instruct。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-10.jpeg\" alt=\"image\" /></p>\n\n<p>我们先来看第一种:偏学术研究的 Instruct。它的核心研究主题是多任务场景下,LLM 模型对 Instruct 理解的泛化能力。如上图中 FLAN 模型所示,就是说有很多 NLP 任务,对于每个任务,研究人员构造一个或者多个 Prompt 模版作为任务的 Instruct,然后用训练例子对 LLM 模型进行微调,让 LLM 以同时学习多个任务。训练好模型后,给 LLM 模型一个它没见过的全新任务的 Instruct,然后让 LLM 解决 zero shot 任务,从任务解决得是否足够好,来判断 LLM 模型是否有对 Instruct 理解的泛化能力。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】FLAN 是什么?</strong>2021 年 9 月 Google Research 团队在文章<a href=\"https://arxiv.org/pdf/2109.01652\">《Finetuned Language Models Are Zero-Shot Learners》</a>中提出的「Finetuned Language 」<br />\n1、FLAN 是 Finetuned LAnguage Net 的缩写,它用 Multi-task Learning 的方法和一种别出心裁的微调方式对 PLM 进行微调,在参数少 400 亿的情况下,性能超越 GPT-3。<br />\n2、部分参考自:https://juejin.cn/post/7064919723498012703 <br />\n3、FLAN 训练:对多个任务,把 Prompt 模板写成 Instruct,以此微调来学习。<br />\n4、FLAN 测试:新类型的任务,直接 zero-shot,判断 LLM 对 Instruct 理解的泛化能力。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】如何增加 LLM 模型 Instruct 泛化能力</strong> <br />\n1、增加训练任务的数量 <br />\n2、增加训练任务的类型多样性 <br />\n3、增加 LLM 模型参数规模 <br />\n4、提供 CoT Prompting</p>\n</blockquote>\n\n<p>如果归纳下目前的研究结论(可参考<a href=\"https://arxiv.org/pdf/2210.11416\">《Scaling Instruction-Finetuned Language Models》</a>/<a href=\"https://arxiv.org/pdf/2204.07705\">《Super-Natural Instructions: Generalization via Declarative Instructions on 1600+ NLP Tasks》</a>),能够有效增加 LLM 模型 Instruct 泛化能力的因素包括:增加多任务的任务数量、增加 LLM 模型大小、提供 CoT Prompting, 以及增加任务的多样性。如果采取任意一项措施,都可以增加 LLM 模型的 Instruct 理解能力。</p>\n\n<p>第二种是人类真实需求下的 Instruct,这类研究以 InstructGPT 和 ChatGPT 为代表。这类工作也是基于多任务的,但是和偏向学术研究类工作最大的不同,在于它是面向人类用户真实需求的。为什么这么说呢?因为它们用于 LLM 多任务训练的任务描述 Prompt,是从大量用户提交的真实请求中抽样而来的,而不是固定好研究任务的范围,然后让研究人员来写任务描述 prompt。这里所谓的「真实需求」,体现在两个方面:首先,因为是从用户提交的任务描述里随机抽取的,所以涵盖的任务类型更多样化,也更符合用户的真实需求;其次,某个任务的 prompt 描述,是用户提交的,体现了一般用户在表达任务需求时会怎么说,而不是你认为用户会怎么说。很明显,这类工作改出来的 LLM 模型,用户体验会更好。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2203.02155.pdf\">InstructGPT 论文</a>里,也拿这种方法和 FLAN 那种 Instruct based 方法做了比较。首先在 GPT3 上用 FLAN 提到的任务、数据以及 Prompt 模版进行微调,来在 GPT 3 上复现 FLAN 方法,然后和 InstructGPT 进行比较,因为 InstructGPT 的基础模型也是 GPT3,所以只有数据和方法的差别,两者可比,结果发现 FLAN 方法的效果,距离 InstructGPT 有很大的差距。那么背后的原因是什么呢?论文分析数据后认为,<strong>FLAN 方法涉及到的任务领域相对少</strong>,是 InstructGPT 涉及领域的子集,所以效果不好。也就是说,<strong>FLAN 论文里涉及到的任务和用户真实需求是不符的</strong>,而这导致在真实场景下效果不够好。而这对我们的启示是:从用户数据中收集真实需求,这事情是很重要的。</p>\n\n<blockquote>\n <p><strong>LLM 技术增量重点</strong>:Instruct <br />\n1、FLAN 的任务领域太少。<br />\n2、FLAN 不是从用户数据收集真实需求(研究人员构造任务),与用户真实需求不符。</p>\n</blockquote>\n\n<h3 id=\"3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</h3>\n\n<p>如果我们假设 In Context Learning 是用一些例子来具象地表达任务命令,Instruct 是一种更符合人类习惯的抽象任务描述。那么,一个很自然的问题是:它们之间有什么联系吗?比如,我们是否能够提供给 LLM 完成某个任务的若干具体示例,让 LLM 找出其对应的自然语言描述的 Instruct 命令?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-11.jpeg\" alt=\"image\" /></p>\n\n<p>目前有零星的工作在探索这个问题,我认为这个方向是很有研究价值的。先说答案,答案是:Yes,LLM Can。<a href=\"https://arxiv.org/pdf/2211.01910\">《Large Language Models Are Human-Level Prompt Engineers》</a>是做这个方向很有趣的工作,如上图所示,对于某项任务,给 LLM 一些示例,让 LLM 自动生成能够描述这项任务的自然语言命令,然后它再用 LLM 生成的任务描述去测试任务效果。它使用的基础模型是 GPT 3 和 InstructGPT,经过这项技术加持后,LLM 生成的 Instruct 的效果相比未采用这项技术的 GPT 3 以及 InstuctGPT 来说,指标有极大地提升,而且在一些任务上超过人类的表现。</p>\n\n<p>这说明了:<strong>具象的任务示例和任务的自然语言描述之间,有种神秘的内在联系。至于这种联系到底是什么?我们目前对此还一无所知</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:Instruct 和 ICL 之间的联系 <br />\n1、ICL 是给了一些具象例子的命令 <br />\n2、Instruct 相当于是抽象命令</p>\n</blockquote>\n\n<h2 id=\"五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</h2>\n\n<p>目前很多研究已证明 LLM 对于知识具有强大的记忆能力,但是,一般我们不会因为一个人记忆能力强,就说这人很聪明,是否具有强大的推理能力,往往是我们判断一个人是否聪明的重要标准。类似的,如果 LLM 的效果想让人觉得很惊艳,强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点,去推导出新知识或新结论。关于 LLM 的推理能力,是最近一年来 LLM 里最重要和热门的研究领域之一。于是,我们关心的问题就是:<strong>LLM 具备推理能力吗?如果具备,那么它的推理能力够强吗?</strong></p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:LLM 具备推理能力吗?</p>\n</blockquote>\n\n<p>这两个问题目前的答案似乎应该是:当模型规模足够大的时候,LLM 本身是具备推理能力的,在简单推理问题上,LLM 已经达到了很好的能力,但是复杂推理问题上,还需要更多深入的研究。</p>\n\n<p>如果梳理现有 LLM 推理相关工作的话,我把它们归到两大类,体现出挖掘或促进 LLM 推理能力不同的技术思路:第一类研究比较多,可以统称为<strong>基于 Prompt 的方法</strong>,核心思想是通过合适的提示语或提示样本,更好地激发出 LLM 本身就具备的推理能力,Google 在这个方向做了大量很有成效的工作。第二类做法是在<strong>预训练过程中引入程序代码</strong>,和文本一起参与预训练,以此进一步增强 LLM 的推理能力,这应该是 OpenAI 实践出的思路。比如 ChatGPT 肯定具备很强的推理能力,但它并不要求用户必须提供一些推理示例,所以 <strong>ChatGPT 强大的推理能力,大概率来源于使用代码参与 GPT 3.5 的预训练</strong>。</p>\n\n<p>这两种思路其实大方向是迥异的:利用代码增强 LLM 推理能力,这体现出一种通过增加多样性的训练数据,来直接增强 LLM 推理能力的思路;而基于 Prompt 的方法,它并不会促进 LLM 本身的推理能力,只是让 LLM 在解决问题过程中更好地展示出这种能力的技术方法。可以看出,前者(代码方法)治本,后者治标。当然,两者其实也是互补的,但从长远看,治本的方法更重要。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】挖掘或促进 LLM 推理能力的两个技术思路</strong>:<br />\n1、Google 有大量研究成果的基于 Prompt 的方法:对应 ICL,挖掘 LLM 的推理能力 —— 基于神奇的 ICL <br />\n2、OpenAI 实践出真知的策略 —— Pre-training 时引入程序代码</p>\n</blockquote>\n\n<h3 id=\"1基于-prompt-的方法\">1、基于 Prompt 的方法</h3>\n\n<p>这方面工作非常多,如果归纳一下的话,大致可以分为三条技术路线。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-12.jpeg\" alt=\"image\" /></p>\n\n<p>第一种思路是直接在问题上追加辅助推理 Prompt。这种方法简单直接,但在众多领域都很有效。这个做法是由<a href=\"https://arxiv.org/pdf/2205.11916\">《Large language models are zero-shot reasoners》</a>提出的,也被称为 zero-shot CoT。具体而言,分为两个阶段(如上图所示),第一阶段在提问的问题上追加「Let’s think step by step」这句提示语,LLM 会输出具体的推理过程;第二阶段,在第一阶段的问题后,拼接 LLM 输出的具体推理过程,并再追加 Prompt=“Therefore, the answer (arabic numerals) is”,此时 LLM 会给出答案。如此简单的操作,却可以大幅增加 LLM 在各项推理任务中的效果,比如在数学推理测试集 GSM8K 上,加上提示语后,推理准确率直接从原先的 10.4% 提升到了 40.4%,可谓神奇。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>加上「Let’s think step by step」立马就提升了 GSM8K 数学推理测试集的准确率 +30pt!</p>\n</blockquote>\n\n<p>为什么 LLM 会具备给一句「Let’s think step by step」提示语,就能列出详细的推理步骤并算出答案呢?其原因目前尚无定论,我的猜测是:<strong>很可能因为预训练数据里面存在大量的此种数据,就是以「Let’s think step by step」开头,然后后面是详细的推理步骤,最后给出答案,而 LLM 在预训练的时候记住了这些模式。而当我们输入这个提示语的时候,激发 LLM 模糊得「回忆」起某些例子的推导步骤,于是即可模仿这些例子进行步骤推理并给出答案</strong>。当然这只是我的无依据推论,若事实真的如此,如果你看过后面介绍的标准 CoT 做法,会发现 Zero-shot CoT 本质上和标准 CoT 很可能没什么区别,只是标准 CoT 由人工来写推理步骤的示例,而 Zero-shot CoT 大概率是通过提示语,激活了记忆中的某些包含推理步骤的示例,很可能是如此区别。而标准 CoT 效果比 Zero-Shot CoT 效果好也完全可以理解,因为毕竟靠 LLM 回忆示例,精准性估计不会太高,而人工给出的示例,准确性是有保障的,所以自然标准 CoT 效果会更好。</p>\n\n<p><strong>这侧面说明了一个道理,就是 LLM 本身是具备推理能力的</strong>,只是我们没有办法把它的这种能力激发出来而已,通过合适的提示语来进行两步提示,就在一定程度上可以释放出它的这种潜力。另外,对于中文,很可能存在另外一个黄金提示语,比如「详细解题思路如下」,类似这种,因为中文语料在讲解推理步骤的时候,经常用的引导句和「让我们一步一步来思考」应该是不同的,这是明显的西方说法,而探索出这个中文黄金提示语,其实也是很有必要的。</p>\n\n<p>第二种思路一般被称为基于示例的思维链(few-shot CoT, Chain of Thought)Prompting。这个方向目前是 LLM 推理研究的主方向,很多工作都是在这个思路上做的,我们简单介绍几个效果显著的代表性工作,基本能代表 CoT 的技术发展方向。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-13.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 的主体思想其实很直白;为了教会 LLM 模型学会推理,给出一些人工写好的推理示例,示例里把得到最终答案前,一步步的具体推理步骤说清楚,而这些人工写的详细推理过程,就是思维链 Prompting,具体例子可参照上图中蓝色文字部分。CoT 的意思是让 LLM 模型明白一个道理;<strong>就是在推理过程中,步子不要迈得太大,否则很容易出错,改变思维模式,化大问题为小问题,步步为营,积小胜为大胜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>这个过程已经从 programming computer 过渡成 teaching computer 了,训练 AI 越来越像一个需要人教育培养的孩子。<br />\nCoT 其实就是给一些思维链 step by step 的 prompting</p>\n</blockquote>\n\n<p>最早明确提出 CoT 这个概念的文章是<a href=\"https://arxiv.org/pdf/2201.11903\">《Chain of thought prompting elicits reasoning in large language models》</a>,论文发布于 22 年 1 月份,虽然做法很简单,但是应用 CoT 后 LLM 模型的推理能力得到了巨大提升,GSM8K 数学推理测试集准确率提高到 60.1% 左右。当然,这种给出详细推理步骤和中间过程的思想,并非 CoT 最早提出的,更早一些的「scratchpad」技术(可参考<a href=\"https://arxiv.org/pdf/2112.00114\">《Show Your Work: Scratchpads for Intermediate Computation with Language Models》</a>)首先采用了类似的思路。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-14.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 提出不久,很快在 22 年 3 月份,一项被称为「Self-Consistency」的改进技术就将 GSM8K 测试集准确率提高到 74.4%,提出这项改进的论文是<a href=\"https://arxiv.org/pdf/2203.11171\">《Self-Consistency Improves Chain of Thought Reasoning in Language Models》</a>。「Self-Consistency」的思路也很直观(参考上图):首先可以利用 CoT 给出几个写了推理过程的示例,然后要求 LLM 对给定的问题进行推理,如果是 CoT,直接输出一个推理过程和答案,整个过程就结束了。「Self-Consistency」则不然,它要求 LLM 输出多个不同的推理过程和答案,然后采用投票的方式选出最佳答案,思路非常简单直接,但是效果也确实好。「Self-Consistency」其实是教导 LLM 学会这么一个道理:孔乙己说过茴香豆的「茴」字有四种写法,类似的,一个数学题的正确解法也可以有很多种,每个不同的推导过程都指向最终的答案。条条大路通罗马,虽说也有个别迷路走到北京的,但是迷路的毕竟是少数,看看大多数人走到哪里,哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义,是不是这道理?</p>\n\n<p>再往后,<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>这个工作在「Self-Consistency」基础上,进一步集成了「从一个 Prompt 问题拓展到多个 Prompt 问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票」这三个改进点,将 GSM8K 测试集准确率提高到 83% 左右。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>GSM8K 数学推理测试集 <br />\n1、提高到 40.4%:加一句「Let’s think step by step」 <br />\n2、提高到 60.1%:应用 CoT 后,即训练时给 LLM 几个写了推理过程的示例 <br />\n3、提高到 74.7%:基于 CoT 的改进技术 Self-Consistency,给出多个不同推理过程和答案,投票选出最好答案 <br />\n4、提高到 83% 左右:基于 Self-Constistenty 的改进技术,1)一个 Prompt 拓展为多个 Prompt;2)检查推理中间步骤正确性;3)对多个输出的回答加权投票。</p>\n</blockquote>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-15.jpeg\" alt=\"image\" /></p>\n\n<p>第三种思路体现了一种分治算法的思想。当然这个所谓「分治」是我归纳的,别人没这么说。这种思路的核心思想是:对于一个复杂的推理问题,我们把它分解成若干容易解决的子问题,一一解决掉子问题后,我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得,这种思路可能才是揭示问题本质、最终解决 LLM 复杂推理问题正宗的道路。我们以「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术为例来说明这种思路的一种具体实现方式,如上图所示:它分为两个阶段,第一个阶段,从原始问题我们可以得知最终要问的问题是什么,我们假设最终问题是 Final Q,然后从原始问题填充 Prompt 模版「如果要解决 Final Q 问题,那么我需要先解决」,然后把原始问题和这个 Prompt 交给 LLM,让 LLM 模型给出答案,等于让 LLM 给出最终问题的前置子问题 Sub Q;接下来我们进入第二个阶段,让 LLM 先回答刚才拿到的子问题Sub Q,并拿到对应的答案,然后原始问题拼接子问题 Sub Q 及对应答案,再去问 LLM 最终那个问题 Final Q,此时LLM会给出最后的答案。如此这般,体现出拆解子问题,并从子问题的答案逐步找出最终答案的思路。</p>\n\n<h3 id=\"2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</h3>\n\n<p>以上是目前利用 Prompt 激发 LLM 模型推理能力的三种主流做法,而关于 LLM 的推理能力,目前还观察到一个有趣且费解的现象:除了文本外,如果能够加入程序代码一起参与模型预训练,则能大幅提升 LLM 模型的推理能力。这个结论从不少论文的实验部分都可以得出(可以参考<a href=\"https://arxiv.org/pdf/2210.03493\">《AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS》</a>/<a href=\"https://arxiv.org/pdf/2210.09261\">《Challenging BIG-Bench tasks and whether chain-of-thought can solve them》</a>等论文的实验部分)。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-16.jpeg\" alt=\"image\" /></p>\n\n<p>上图给出了一份实验数据,来自于论文<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>,其中 GPT3 davinci 就是标准的 GPT 3 模型,基于纯文本训练;code-davinci-002(OpenAI 内部称为 Codex)是同时在 Code 和 NLP 数据上训练的模型。如果比较两者效果,可以看出,不论采用具体哪种推理方法,仅仅是从纯文本预训练模型切换到文本和 Code 混合预训练模型,在几乎所有测试数据集合上,模型推理能力都得到了巨大的效果提升,比如我们以「Self Consistency」方法为例,在大多数据集合上的性能提升,都直接超过了 20 到 50 个百分点,这是很恐怖的性能提升,而其实在具体推理模型层面,我们什么也没做,仅仅是预训练的时候除了文本,额外加入了程序代码而已。</p>\n\n<p>除了这个现象,从上图数据中,我们还可以得出其它一些结论,比如 GPT 3 这种纯文本预训练模型,其实是具备相当程度的推理能力的,除了在 GSM8K 这种数学推理上效果比较差外,其它推理数据数据集合表现也还可以,前提你需要采用合适的方法,来激发出它本身就具备的这种能力;再比如,text-davinci-002,也就是在 code-davinci-002 基础上加入 instruct fine-tuning 后的模型(就是加入 InstructGPT 或 ChatGPT 模型的第一步),其推理能力要弱于 Codex,但是有其它研究表明它在自然语言处理任务又要强于 Codex。而这貌似说明了,加入 instruct fine-tuning,会损害 LLM 模型的推理能力,但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的,也能启发后续进一步的思考和探索。</p>\n\n<p>那么,一个自然的疑问是:<strong>为何预训练模型可以从代码的预训练中获得额外的推理能力?确切原因目前未知,值得深入探索</strong>。我猜测可能是因为原始版本的 Codex(只使用代码训练,可参考文献:<a href=\"https://arxiv.org/pdf/2107.03374\">《Evaluating Large Language Models Trained on Code》</a>)的代码训练是从文本生成代码,而且代码中往往包含很多文本注释,本质上这类似于预训练模型做了 <文本,Code> 两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释,很明显这些数学类或逻辑推理类的数据,对于解决下游数学推理问题是有帮助的,我猜大概率原因在此。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么预训练模型可以从代码预训练中获得额外的推力能力?</p>\n</blockquote>\n\n<h3 id=\"3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</h3>\n\n<p>上面介绍了 LLM 推理的主流技术思路和现有的一些结论,接下来谈谈我对 LLM 模型推理技术的思考,以下内容纯个人推断,没有太多证据,还请谨慎参考。我的判断是:虽然最近一年来,关于激发 LLM 的推理能力,这方面的技术进展很快,也取得了很大的技术进步,但是总体感觉是,我们可能走在正确的方向上,但是距离接触到真正的问题本质还有一段距离,对此要有更深入的思考和探索。</p>\n\n<p>首先,我比较赞同上述分治算法的主体思路,对于复杂的推理问题,我们应该把它拆解成若干简单的子问题,因为子问题对于 LLM 来说回答正确的概率就大很多,让 LLM 一一 回答子问题后,再逐步推导出最终答案。受到「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术的启发,如果进一步思考,我觉得 LLM 推理本质上很可能会是如下两种可能的其中之一:不断和 LLM 进行交互的图上推理问题,抑或是不断和LLM进行交互的程序流程图执行问题。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-17.jpeg\" alt=\"image\" /></p>\n\n<p>先说图上推理问题,如上图所示,假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构,图中的节点是子问题或者子步骤,图中的边代表了子问题之间的依赖关系,就是说只有回答好子问题 A,才能回答子问题 B,而且图中大概率存在循环结构,就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图,那么可以根据依赖关系,引导 LLM 一步一步按照图结构,回答必须首先回答的子问题,直到推导出最终答案。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-18.jpeg\" alt=\"image\" /></p>\n\n<p>再说程序流程图问题,参考上图,假设我们有办法把复杂问题拆解成子问题或子步骤,并产生一个由子步骤构成的类似程序流程图的结构,在这个结构里,有些步骤会反复执行多次(循环结构),有些步骤的执行需要进行条件判断(条件分支)。总而言之,在执行每个子步骤的时候和 LLM 进行交互,得到子步骤的答案,然后按照流程不断执行,直到输出最终答案。类似这种模式。假设这个思路大致正确的话,也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力:大概率因为 <文本,代码> 的多模态预训练模型,在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁,将两者联系起来的,即由文本描述到隐含的流程图,再映射到由流程图产生具体的代码。也就是说,这种多模态预训练,可以增强 LLM 模型从文本构建出隐含的流程图并按照流程图执行的能力,也就是加强了它的推理能力。</p>\n\n<p>当然,上述思路最大的问题是,我们如何根据文本描述的问题,能够靠 LLM 模型,或者其它模型,得到图结构或者流程图结构?这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练,走隐式学习内部隐含结构的方法。而目前的 CoT 技术,如果套到上述思路来思考的话,可以这么理解:标准 CoT,其实就是靠自然语言文本来描述图结构或者程序流程图的;而「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术,则是试图根据最后一个图节点,靠倒推来试图推导出其中的图结构,但是很明显,目前的方法限制了它倒推的深度,也就是说它只能推导出非常简单的图结构,这正是限制它能力的所在。</p>\n\n<h2 id=\"六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</h2>\n\n<p>这里列出一些我个人认为比较重要的 LLM 研究领域,或值得深入探索的研究方向。</p>\n\n<h4 id=\"探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</h4>\n\n<p>尽管继续推大 LLM 模型的规模,这事看似没有技术含量,但是其实这个事情异常重要。我个人判断,自从 Bert 出现以来,到 GPT 3,再到 ChatGPT,大概率这些给人印象深刻的关键技术突破,核心贡献都来自于 LLM 模型规模的增长,而非某项具体技术。说不定,揭开 AGI 真正的钥匙就是:超大规模及足够多样性的数据、超大规模的模型,以及充分的训练过程。再者,做超大规模的 LLM 模型,对技术团队的工程实现能力要求是非常高的,也不能认为这事情缺乏技术含量。</p>\n\n<p>那么继续推大 LLM 模型规模,有什么研究意义呢?我觉得有两方面的价值。首先,如上所述,我们已知,对于知识密集型的任务,随着模型规模越大,各种任务的效果会越来越好;而对很多推理类型的有难度的任务,加上 CoT Prompting 后,其效果也呈现出遵循 Scaling law 的趋向。那么,很自然的一个问题就是:对于这些任务,LLM 的规模效应,能将这些任务解决到何种程度?这是包括我在内,很多人关心的问题。其次,考虑到 LLM 具备的神奇的「涌现能力」,如果我们继续增加模型规模,它会解锁哪些让我们意想不到的新能力呢?这也是很有意思的问题。考虑到以上两点,我们仍然需要不断增大模型规模,看看模型规模对解决各类任务的天花板在哪里。</p>\n\n<p>当然,这种事情也就只能说说,对 99.99% 的从业者来说,是没有机会和能力做这个事情的。要做这个事情,对研究机构的财力及投入意愿、工程能力、技术热情,都有极高的要求,缺一不可。能做这事情的机构,粗估下来,国外不超过 5 家,国内不超过 3 家。当然,考虑到成本问题,未来也许会出现「股份制大模型」,就是有能力的几家机构合作,群策群力,一起来共建超级大模型的现象。</p>\n\n<h4 id=\"增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</h4>\n\n<p>正如之前对 LLM 推理能力的叙述,尽管 LLM 在最近一年推理能力得到了很大的提升,但是很多研究(参考<a href=\"https://arxiv.org/pdf/2208.05051\">《Limitations of Language Models in Arithmetic and Symbolic Induction》</a>/<a href=\"https://arxiv.org/pdf/2206.10498\">《Large Language Models Still Can’t Plan》</a>)表明,目前 LLM 能够解决得比较好的推理问题,往往都相对简单,LLM 的复杂推理能力仍然薄弱,比如即使是简单的字符拷贝推理或者加减乘除运算,当字符串或者数字非常长的时候,LLM 推理能力会极速下降,再比如行为规划能力等复杂推理能力很弱。总而言之,加强 LLM 的复杂推理能力,应该是 LLM 未来研究中最重要的环节之一。</p>\n\n<p>前文有述,<strong>加入代码加入预训练,这是一种直接增强 LLM 推理能力的方向。这个方向目前研究尚显不足,更像是实践经验的总结</strong>,探索背后的原理,并进而引入更多类型除代码外的新型数据来增强 LLM 的推理能力,这可能是更本质提升推理能力的方向。</p>\n\n<h4 id=\"llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</h4>\n\n<p>目前的 ChatGPT 擅长 NLP 和 Code 任务,作为通向 AGI 的重要种子选手,<strong>将图像、视频、音频等图像与多模态集成进入 LLM,乃至 AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入 LLM</strong>,是 LLM 通往 AGI 的必经之路。而这个方向才刚刚开始,因此具备<strong>很高的研究价值</strong>。</p>\n\n<h4 id=\"更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</h4>\n\n<p>如前所述,ChatGPT 的最大技术贡献即在此。但是很明显,目前的技术并不完美,肯定还有很多命令 LLM 理解不了。所以,沿着这个方向,寻找更好的技术,<strong>来让人类使用自己习惯的命令表达方式,而 LLM 又能听懂,这是个新的,且非常有前景的技术方向</strong>。</p>\n\n<h4 id=\"建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</h4>\n\n<p>好的评测数据集,是引导技术不断进步的基石。随着 LLM 模型逐步增大,任务效果快速提升,导致很多标准测试集快速过时。也就是说,这些数据集合相对现有技术来说,太容易了,在没有难度的测试集合下,我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合,是促进 LLM 技术进步的关键所在。</p>\n\n<p>目前行业应出现了一些新的测试集,有代表性的包括 BIGBench、OPT-IML 等。这些测试集合体现出一些特性,比如相对 LLM 现有技术具备一定的难度、综合了各种各样多种类型的任务等。</p>\n\n<p>受到 ChatGPT 的启发,我觉得除此外应纳入另一考虑因素:体现真实用户需求。就是说,这些任务的表述由用户真实发起,这种方式构建出来的 LLM 模型,才能解决用户实际需求。</p>\n\n<p>除此外,相信 LLM 会快速将能力溢出到 NLP 之外的领域,而如何融入更多其它领域的评测数据,也是需要提前去考虑。</p>\n\n<h4 id=\"高质量数据工程\">高质量数据工程</h4>\n\n<p>对于预训练模型来说,数据是其根本,预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此,我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。</p>\n\n<p>关于数据,需要考虑两个方面:数据的质量和数量。而根据 T5 的对比实验,我们可以得出结论:在数量和质量两个因素里,质量优先,正确的道路应该是在保证数据质量的前提下,再去增大数据规模。</p>\n\n<p>数据质量,包括数据的信息含量以及数据的多样性等多个衡量标准,比如 Wiki 明显就属于世界知识密度极高的高质量数据,这是从信息含量来说的;而增加数据类型的多样性,无疑是激发 LLM 各种新能力的根本,比如加入问答网站的数据,对于 LLM 的 QA 能力提升是有直接帮助的。多样化的数据赋予了 LLM 更好解决更多不同类型任务的能力,所以,这可能是数据质量里最关键的标准。</p>\n\n<p>关于数据数量,原则上互联网上公开发布的数据都可以纳入 LLM 模型的预训练过程。那么,它的极限在哪里?<a href=\"https://arxiv.org/pdf/2211.04325\">《Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning》</a>对此进行了估算,结论是到 2026 年左右,高质量的NLP数据将会用光,低质量 NLP 数据会在 2030 到 2050 年用光,而低质量图像数据会在 2030 到 2060 年用光。而这意味着:要么到时我们有新类型的数据源,要么我们必须增加 LLM 模型对数据的利用效率。否则,目前这种数据驱动的模型优化方式将会停止进步,或者收益减少。</p>\n\n<h4 id=\"超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</h4>\n\n<p>目前规模最大的 LLM 中,有相当比例的模型采取了稀疏(Sparse)结构,比如 GPT 3、PaLM、GLaM 等,GPT 4 大概率也会走稀疏模型路线。之所以采用 Sparse 化的模型,主要好处是它可以极大减少 LLM 的训练时间和在线推理时间。Switch Transformer 论文里指出:在相同算力预算的前提下,使用稀疏化 Transformer,相对 Dense Transformer,LLM 模型的训练速度可以提升 4 倍到 7 倍。为何 Sparse 模型可以加快训练和推理时间呢?这是因为尽管模型参数巨大,但是对于某个训练实例,Sparse 模型通过路由机制,只使用整个参数中的一小部分,参与训练和推理的活跃参数量比较少,所以速度快。</p>\n\n<p>我认为未来超大的 LLM 模型大概率会收敛到稀疏模型。主要有两个原因:一方面,现有研究表明(参考<a href=\"https://arxiv.org/pdf/2210.06313\">《Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers》</a>),标准的 Dense Transformer在训练和推理时,它本身也是稀疏激活的,就是说只有部分参数会被激活,大部分参数没有参与训练和推理过程。既然这样,我们不如直接迁移到稀疏模型;另外,毫无疑问 LLM 模型的规模会继续推大,而高昂的训练成本是妨碍其进一步扩大模型的重要阻力,<strong>使用稀疏模型可以极大降低超大模型的训练成本</strong>,所以随着模型规模越大,稀疏模型带来的收益越明显。考虑到这两个方面,大概率未来更大的 LLM 模型会采用稀疏模型方案。</p>\n\n<p>那为何目前其它大规模模型不走稀疏模型的路线呢?因为 Sparse 模型存在训练不稳定、容易过拟合等问题,不太容易训练好。所以,如何修正稀疏模型面临的问题,<strong>设计出更容易训练的稀疏模型,是很重要的未来研究方向</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>重要研究拓展方向 <br />\n1、任务层 —— 增加 LLM 推理能力 <br />\n2、接口层 —— 命令更自然:怎么把 ICL 逐渐过渡成 zero-shot 的 Instruct <br />\n3、任务层 —— 多模态拓展:先在 NLP 和 Code 上奔向 AGI,把 CV、音频、AI for science、自动驾驶、机器人逐渐囊括进来 <br />\n4、模型层 —— 训练更容易:稀疏矩阵问题很多(训练不稳定、容易过拟合)的解决</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>为什么稀疏结构效果好?<br />\n1、计算快:如果很稠密,则对很多知识的提炼都耦合在了一起。如果比较稀疏,就类似人脑,对不同功能、不同知识等内容是分开存储的,耦合少、计算速度就快。<br />\n2、好训练:越稀疏训练成本越低\n3、缺点:训练不稳定,容易过拟合</p>\n</blockquote>\n\n<h2 id=\"七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</h2>\n\n<p>如果希望能复刻类似 ChatGPT 这种效果令人惊艳的 LLM 模型,综合目前的各种研究结论,在做技术选型时需要重点权衡如下问题:</p>\n\n<p>首先,在预训练模式上,我们有三种选择:GPT 这种自回归语言模型,Bert 这种双向语言模型,以及 T5 这种混合模式(Encoder-Decoder 架构,在 Encoder 采取双向语言模型,Decoder 采取自回归语言模型,所以是一种混合结构,但其本质仍属于 Bert 模式)。我们应选择 GPT 这种自回归语言模型,其原因在本文范式转换部分有做分析。目前看,<strong>国内 LLM 在做这方面技术选型的时候,貌似很多都走了 Bert 双向语言模型或 T5 混合语言模型的技术路线,很可能方向走偏了</strong>。</p>\n\n<p>第二,<strong>强大的推理能力是让用户认可 LLM 的重要心理基础</strong>,而如果希望 LLM 能够具备强大的推理能力,根据目前经验,最好在做预训练的时候,要引入大量代码和文本一起进行 LLM 训练。至于其中的道理,在本文前面相关部分有对应分析。</p>\n\n<p>第三,如果希望模型参数规模不要那么巨大,但又希望效果仍然足够好,此时有两个技术选项可做配置:要么增强高质量数据收集、挖掘、清理等方面的工作,意思是我模型参数可以是 ChatGPT / GPT 4 的一半,但是要想达到类似的效果,那么高质量训练数据的数量就需要是 ChatGPT/GPT 4 模型的一倍(Chinchilla 的路子);另外一个可以有效减小模型规模的路线是采取文本检索(Retrieval based)模型 + LLM 的路线,这样也可以在效果相当的前提下,极大减少 LLM 模型的参数规模。这两个技术选型不互斥,反而是互补的,也即是说,可以同时采取这两个技术,在模型规模相对比较小的前提下,达到超级大模型类似的效果。</p>\n\n<p>第四,超级大模型因为模型规模大,所以训练成本过高,导致很少有机构有能力去做这件事。而且由上文分析可见,继续不断推大 LLM 模型规模是肯定会发生、也应该去做的事情。于是,如何通过技术手段降低 LLM 的训练成本就很重要。LLM 的特征抽取器 Sparse 化是有效降低模型训练及推理成本的技术选择。由此可见,随着模型越来越大,LLM 模型 Sparse 化是一个应该考虑的选项。</p>\n\n<p>第五,ChatGPT 是目前最接近理想 LLM 的技术方案,而理想中的 LLM 应该是以一个几乎无所不能的基础通用大模型作为依托,来支持各种各样的上层任务类型。目前看,支持越来越多的任务类型,主要是通过增加 LLM 预训练数据的多样性来达成的,数据多样性越好,LLM 能够支持的任务类型就越丰富。所以,<strong>应该重视通过增加数据多样性来增加 LLM 新能力的思路</strong>。</p>\n\n<p>第六,易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务,而 LLM 要能够理解这些 Instruct 的真实含义。另外,也要注意这些 Instruct 是符合人类真实需求的,就是说,要从最终用户那里收集任务表述方式,而不能靠研发人员自己的臆想或猜测。ChatGPT 给我最大的启发其实是这一点,至于是否用增强学习我倒觉得不重要,其它替代技术应该也能做类似的事情。</p>\n\n<h2 id=\"八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</h2>\n\n<p>为什么是 OpenAI 作出了 ChatGPT,而不是其它机构呢?我们在这里可以做个简单分析。</p>\n\n<p>在本文开头,我们提到了 OpenAI 看待 LLM 的理念。OpenAI 是怎么看待 LLM 的呢?回顾它不断推出的技术,可以看出,它其实从 GPT 1.0 开始,基本就坚定地把 LLM 看做是通往 AGI 的一条必由之路。具体而言,在 OpenAI 眼中,未来的 AGI 应该长这个样子:有一个任务无关的超大型 LLM,用来从海量数据中学习各种知识,这个 LLM 以生成一切的方式,来解决各种各样的实际问题,而且它应该能听懂人类的命令,以便于人类使用。其实对 LLM 发展理念的理解,在前半部分,就是「构建一个任务无关的超大型 LLM,让它从海量数据中学习各种知识」,这一点几乎是大家的共识,能体现出 OpenAI 眼光的其实是后半部分。</p>\n\n<p>OpenAI 的理念比较超前,对自我定位从一开始就定得比较高,始终坚定不移地探索上述方式是否可以实现 AGI。OpenAI 之所以能作出 ChatGPT,胜在一个是定位比较高,另一个是不受外界干扰,态度上坚定不移。</p>\n\n<p>我们可以回顾下它走的一些关键路程:GPT 1.0 走的是生成模式的自回归语言模型路线,比 Bert 出来的还早些。Bert 证明了:双向语言模型对于很多 NLP 理解类任务,效果比自回归这种单向语言模型效果更好。尽管如此,GPT 2.0 并没有因此切换到双向语言模型这条路上,仍然走文本生成的路,而且开始尝试零示例(zero shot)prompt 和少量示例(few shot)prompt。其实这时候, OpenAI 心目中的 AGI 已经开始浮出水面,逐渐显示出轮廓了。只是因为 zero shot/few shot 效果比 Bert+fine-tuning 差的比较远,所以大家都没太当回事,甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候,我估计即使是 OpenAI 自己,也不一定能确保这条路肯定能走通。</p>\n\n<p>但是,这不妨碍它继续在这条路上往后走。GPT 3.0 已经展示出了比较强大的 zero shot/few shot prompt 能力,这时候 OpenAI 心目中的 AGI 已经完全漏出水面,轮廓清晰,而且它的效果也证明了这条路,是有较大可能走得通的。GPT 3.0 是一个决定 LLM 发展方向的叉路口和分水岭,与之对应的另外一条路是「Bert+fine-tuning」模式。在这个岔路口,不同的从业者选择走上了不同的道路,后面的技术差距也是从这里开始拉开的。很遗憾地是,国内很多从业者选择继续在「Bert+fine-tuning」这条路上往后走,这也是造成今天落后局面的一个关键时间节点。再往后,就是 InstructGPT 和 ChatGPT 了,OpenAI 通过 ChatGPT 证明了一点;虽然我们距离真正的 AGI,可能还有很长的路要走,但是通过超大 LLM 走向 AGI 这条路,目前看是可行的。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"自注意力":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Self-Attention":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"多头注意力":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Multiple Head Attention":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"残差网络":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Short-Cut":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"位置编码":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Bahdanau":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Encoder-Decoder":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"diffusion":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"MidJourney":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Text2Image":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"文生图":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"ChatGPT":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</title>\n \t<meta name=\"description\" content=\"最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是「Optimizing Language Models for Dialogue」,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</h2>\t\t\n\t<time datetime=\"2022-12-11T15:59:57+00:00\" class=\"by-line\">11 Dec 2022, 杭州 | 麦克船长 | 总计 1692 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-12-11-wechat-chatgpt-3.png\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n<p>最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是 <strong>「Optimizing Language Models for Dialogue」</strong>,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。</p>\n\n<h3 id=\"stepbystep\">Step by step</h3>\n\n<p>本实践依赖:CLI、Docker、npm、Github、fuergaosi233/wechat-chatgpt、git、YAML、Chrome 的使用。以下将简洁地 Step by step 列出步骤。</p>\n\n<p>第一步,你要现有一个 OpenAI 的账号,注意注册时手机号不能是中国大陆或香港的,IP 地址和 GPS 也不能暴露你是中国大陆或者香港的。</p>\n\n<p>第二步,准备一台服务器(否则个人电脑要一直处于开机运行状态),由于后面将用到 Session Token 来登录,因此 IP 地址是香港也没关系,于是我是在我的香港服务器上部署 wechat-chatgpt</p>\n\n<p>第三步,在服务器上安装 Docker,不赘述。</p>\n\n<p>第四步,从 Github 上拉取项目项目到服务器上。</p>\n\n<p>第五步,任何设备上登录 ChatGPT,用 Chrome 的 Inspect 来查看并复制 session token 到剪贴板。</p>\n\n<p>第六步,编辑 wechat-chatgpt 的 config.yaml,填写 session token;设置 private trigger keywords(可选)。</p>\n\n<div class=\"language-yaml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"na\">chatGPTAccountPool</span><span class=\"pi\">:</span>\n <span class=\"pi\">-</span> <span class=\"na\">email</span><span class=\"pi\">:</span> <span class=\"s\"><your email></span>\n <span class=\"na\">password</span><span class=\"pi\">:</span> <span class=\"s\"><your password></span>\n<span class=\"c1\"># if you hope only some keywords can trigger chatgpt on private chat, you can set it like this:</span>\n<span class=\"na\">chatPrivateTiggerKeyword</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">\"</span>\n</code></pre></div></div>\n\n<p>第七步,用 docker 来拉取 wechat-chatgpt</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker pull holegots/wechat-chatgpt:latest。\n</code></pre></div></div>\n\n<p>第八步,启动 wechat-chatgpt:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker run <span class=\"nt\">-d</span> <span class=\"nt\">--name</span> wechat-chatgpt <span class=\"nt\">-v</span> <span class=\"si\">$(</span><span class=\"nb\">pwd</span><span class=\"si\">)</span>/config.yaml:/app/config.yaml holegots/wechat-chatgpt:latest\n</code></pre></div></div>\n\n<p>注意,如果手动模式下也可以用npm run dev启动。如果提示系统不认识 npm 则可以运行 <code class=\"language-plaintext highlighter-rouge\">npm install && poetry install</code> 来解决。到此你就可以在微信上跟这个打通了 ChatGPT 的账号聊天了。</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-1.png\" alt=\"image\" style=\"width:100%\" /></th>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-2.png\" alt=\"image\" style=\"width:100%\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>其实可以看到这个 AI 船长不管是专业性问题(计算机相关)还是非专业问题,都回答的很不错。</p>\n\n<p>如何停止、重启、查看日志呢?首先停止的命令是docker stop wechat-chatgpt,登录时需要扫码登录微信并追踪 logs,因为这其实是用了微信在桌面端的接口。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker logs <span class=\"nt\">-f</span> wechat-chatgpt\n</code></pre></div></div>\n\n<p>会在 Terminal 里显示一个文字阵列组成的桌面端微信登录二维码,用你打算做成微信 AI 机器人那个微信号扫一下,相关信息都填完。另外,这样最好别用自己的微信大号,而是用一个小号。微信不让聊这些,小号注意要完成实名认证。</p>\n\n<p>如果要停止运行,用如下命令:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker stop wechat-chatgpt\n</code></pre></div></div>\n\n<h3 id=\"参考\">参考</h3>\n\n<p>1、<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/tree/main\">https://github.com/fuergaosi233/wechat-chatgpt/tree/main</a></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"OpenAI":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</title>\n \t<meta name=\"description\" content=\"最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是「Optimizing Language Models for Dialogue」,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</h2>\t\t\n\t<time datetime=\"2022-12-11T15:59:57+00:00\" class=\"by-line\">11 Dec 2022, 杭州 | 麦克船长 | 总计 1692 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-12-11-wechat-chatgpt-3.png\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n<p>最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是 <strong>「Optimizing Language Models for Dialogue」</strong>,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。</p>\n\n<h3 id=\"stepbystep\">Step by step</h3>\n\n<p>本实践依赖:CLI、Docker、npm、Github、fuergaosi233/wechat-chatgpt、git、YAML、Chrome 的使用。以下将简洁地 Step by step 列出步骤。</p>\n\n<p>第一步,你要现有一个 OpenAI 的账号,注意注册时手机号不能是中国大陆或香港的,IP 地址和 GPS 也不能暴露你是中国大陆或者香港的。</p>\n\n<p>第二步,准备一台服务器(否则个人电脑要一直处于开机运行状态),由于后面将用到 Session Token 来登录,因此 IP 地址是香港也没关系,于是我是在我的香港服务器上部署 wechat-chatgpt</p>\n\n<p>第三步,在服务器上安装 Docker,不赘述。</p>\n\n<p>第四步,从 Github 上拉取项目项目到服务器上。</p>\n\n<p>第五步,任何设备上登录 ChatGPT,用 Chrome 的 Inspect 来查看并复制 session token 到剪贴板。</p>\n\n<p>第六步,编辑 wechat-chatgpt 的 config.yaml,填写 session token;设置 private trigger keywords(可选)。</p>\n\n<div class=\"language-yaml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"na\">chatGPTAccountPool</span><span class=\"pi\">:</span>\n <span class=\"pi\">-</span> <span class=\"na\">email</span><span class=\"pi\">:</span> <span class=\"s\"><your email></span>\n <span class=\"na\">password</span><span class=\"pi\">:</span> <span class=\"s\"><your password></span>\n<span class=\"c1\"># if you hope only some keywords can trigger chatgpt on private chat, you can set it like this:</span>\n<span class=\"na\">chatPrivateTiggerKeyword</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">\"</span>\n</code></pre></div></div>\n\n<p>第七步,用 docker 来拉取 wechat-chatgpt</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker pull holegots/wechat-chatgpt:latest。\n</code></pre></div></div>\n\n<p>第八步,启动 wechat-chatgpt:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker run <span class=\"nt\">-d</span> <span class=\"nt\">--name</span> wechat-chatgpt <span class=\"nt\">-v</span> <span class=\"si\">$(</span><span class=\"nb\">pwd</span><span class=\"si\">)</span>/config.yaml:/app/config.yaml holegots/wechat-chatgpt:latest\n</code></pre></div></div>\n\n<p>注意,如果手动模式下也可以用npm run dev启动。如果提示系统不认识 npm 则可以运行 <code class=\"language-plaintext highlighter-rouge\">npm install && poetry install</code> 来解决。到此你就可以在微信上跟这个打通了 ChatGPT 的账号聊天了。</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-1.png\" alt=\"image\" style=\"width:100%\" /></th>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-2.png\" alt=\"image\" style=\"width:100%\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>其实可以看到这个 AI 船长不管是专业性问题(计算机相关)还是非专业问题,都回答的很不错。</p>\n\n<p>如何停止、重启、查看日志呢?首先停止的命令是docker stop wechat-chatgpt,登录时需要扫码登录微信并追踪 logs,因为这其实是用了微信在桌面端的接口。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker logs <span class=\"nt\">-f</span> wechat-chatgpt\n</code></pre></div></div>\n\n<p>会在 Terminal 里显示一个文字阵列组成的桌面端微信登录二维码,用你打算做成微信 AI 机器人那个微信号扫一下,相关信息都填完。另外,这样最好别用自己的微信大号,而是用一个小号。微信不让聊这些,小号注意要完成实名认证。</p>\n\n<p>如果要停止运行,用如下命令:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker stop wechat-chatgpt\n</code></pre></div></div>\n\n<h3 id=\"参考\">参考</h3>\n\n<p>1、<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/tree/main\">https://github.com/fuergaosi233/wechat-chatgpt/tree/main</a></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"微信":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</title>\n \t<meta name=\"description\" content=\"最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是「Optimizing Language Models for Dialogue」,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</h2>\t\t\n\t<time datetime=\"2022-12-11T15:59:57+00:00\" class=\"by-line\">11 Dec 2022, 杭州 | 麦克船长 | 总计 1692 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-12-11-wechat-chatgpt-3.png\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n<p>最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是 <strong>「Optimizing Language Models for Dialogue」</strong>,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。</p>\n\n<h3 id=\"stepbystep\">Step by step</h3>\n\n<p>本实践依赖:CLI、Docker、npm、Github、fuergaosi233/wechat-chatgpt、git、YAML、Chrome 的使用。以下将简洁地 Step by step 列出步骤。</p>\n\n<p>第一步,你要现有一个 OpenAI 的账号,注意注册时手机号不能是中国大陆或香港的,IP 地址和 GPS 也不能暴露你是中国大陆或者香港的。</p>\n\n<p>第二步,准备一台服务器(否则个人电脑要一直处于开机运行状态),由于后面将用到 Session Token 来登录,因此 IP 地址是香港也没关系,于是我是在我的香港服务器上部署 wechat-chatgpt</p>\n\n<p>第三步,在服务器上安装 Docker,不赘述。</p>\n\n<p>第四步,从 Github 上拉取项目项目到服务器上。</p>\n\n<p>第五步,任何设备上登录 ChatGPT,用 Chrome 的 Inspect 来查看并复制 session token 到剪贴板。</p>\n\n<p>第六步,编辑 wechat-chatgpt 的 config.yaml,填写 session token;设置 private trigger keywords(可选)。</p>\n\n<div class=\"language-yaml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"na\">chatGPTAccountPool</span><span class=\"pi\">:</span>\n <span class=\"pi\">-</span> <span class=\"na\">email</span><span class=\"pi\">:</span> <span class=\"s\"><your email></span>\n <span class=\"na\">password</span><span class=\"pi\">:</span> <span class=\"s\"><your password></span>\n<span class=\"c1\"># if you hope only some keywords can trigger chatgpt on private chat, you can set it like this:</span>\n<span class=\"na\">chatPrivateTiggerKeyword</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">\"</span>\n</code></pre></div></div>\n\n<p>第七步,用 docker 来拉取 wechat-chatgpt</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker pull holegots/wechat-chatgpt:latest。\n</code></pre></div></div>\n\n<p>第八步,启动 wechat-chatgpt:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker run <span class=\"nt\">-d</span> <span class=\"nt\">--name</span> wechat-chatgpt <span class=\"nt\">-v</span> <span class=\"si\">$(</span><span class=\"nb\">pwd</span><span class=\"si\">)</span>/config.yaml:/app/config.yaml holegots/wechat-chatgpt:latest\n</code></pre></div></div>\n\n<p>注意,如果手动模式下也可以用npm run dev启动。如果提示系统不认识 npm 则可以运行 <code class=\"language-plaintext highlighter-rouge\">npm install && poetry install</code> 来解决。到此你就可以在微信上跟这个打通了 ChatGPT 的账号聊天了。</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-1.png\" alt=\"image\" style=\"width:100%\" /></th>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-2.png\" alt=\"image\" style=\"width:100%\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>其实可以看到这个 AI 船长不管是专业性问题(计算机相关)还是非专业问题,都回答的很不错。</p>\n\n<p>如何停止、重启、查看日志呢?首先停止的命令是docker stop wechat-chatgpt,登录时需要扫码登录微信并追踪 logs,因为这其实是用了微信在桌面端的接口。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker logs <span class=\"nt\">-f</span> wechat-chatgpt\n</code></pre></div></div>\n\n<p>会在 Terminal 里显示一个文字阵列组成的桌面端微信登录二维码,用你打算做成微信 AI 机器人那个微信号扫一下,相关信息都填完。另外,这样最好别用自己的微信大号,而是用一个小号。微信不让聊这些,小号注意要完成实名认证。</p>\n\n<p>如果要停止运行,用如下命令:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker stop wechat-chatgpt\n</code></pre></div></div>\n\n<h3 id=\"参考\">参考</h3>\n\n<p>1、<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/tree/main\">https://github.com/fuergaosi233/wechat-chatgpt/tree/main</a></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"BERT":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例</title>\n \t<meta name=\"description\" content=\"2018 年 Google 发布了 BERT 模型后迅速席卷 NLP 领域,这家伙可是比 ChatGPT 背后的 GPT 还要早的。本文简单介绍了 BERT 后主要是希望大家都手试一下,所以文中提到了一个小的中文模型供大家练手,以及一个小用例。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例</h2>\t\t\n\t<time datetime=\"2022-12-17T15:08:01+00:00\" class=\"by-line\">17 Dec 2022, 杭州 | 麦克船长 | 总计 7275 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一关于-bert-的一些背景\" id=\"markdown-toc-一关于-bert-的一些背景\">一、关于 BERT 的一些背景</a></li>\n <li><a href=\"#二开始一个-bert-的动手小试验\" id=\"markdown-toc-二开始一个-bert-的动手小试验\">二、开始一个 BERT 的动手小试验</a> <ul>\n <li><a href=\"#1安装-anaconda-来为部署-bert-做环境准备\" id=\"markdown-toc-1安装-anaconda-来为部署-bert-做环境准备\">1、安装 Anaconda 来为部署 BERT 做环境准备</a></li>\n <li><a href=\"#2安装-bert-所需要的各种依赖\" id=\"markdown-toc-2安装-bert-所需要的各种依赖\">2、安装 BERT 所需要的各种依赖</a></li>\n <li><a href=\"#3下载一个预训练pre-train过的-bert-模型\" id=\"markdown-toc-3下载一个预训练pre-train过的-bert-模型\">3、下载一个预训练(Pre-Train)过的 BERT 模型</a></li>\n <li><a href=\"#5启动-bert-服务端\" id=\"markdown-toc-5启动-bert-服务端\">5、启动 BERT 服务端</a></li>\n <li><a href=\"#6在-pycharm-中使用-conda-的环境\" id=\"markdown-toc-6在-pycharm-中使用-conda-的环境\">6、在 PyCharm 中使用 Conda 的环境</a></li>\n <li><a href=\"#7编写程序实现-bert-客户端\" id=\"markdown-toc-7编写程序实现-bert-客户端\">7、编写程序实现 BERT 客户端</a></li>\n </ul>\n </li>\n <li><a href=\"#三bert-模型的优劣势及其原因\" id=\"markdown-toc-三bert-模型的优劣势及其原因\">三、BERT 模型的优劣势及其原因</a> <ul>\n <li><a href=\"#1bert-的优势是很明显的\" id=\"markdown-toc-1bert-的优势是很明显的\">1、BERT 的优势是很明显的</a> <ul>\n <li><a href=\"#11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\" id=\"markdown-toc-11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\">1.1、MLM 和 NSP 预训练能够捕捉到自然语言中的各种复杂细节</a></li>\n <li><a href=\"#12识别并专注于较重要的部分进行文本处理\" id=\"markdown-toc-12识别并专注于较重要的部分进行文本处理\">1.2、识别并专注于较重要的部分进行文本处理</a></li>\n <li><a href=\"#13快速构建针对具体任务的-nlp-系统\" id=\"markdown-toc-13快速构建针对具体任务的-nlp-系统\">1.3、快速构建针对具体任务的 NLP 系统</a></li>\n </ul>\n </li>\n <li><a href=\"#2bert-模型的劣势及其原因\" id=\"markdown-toc-2bert-模型的劣势及其原因\">2、BERT 模型的劣势及其原因</a> <ul>\n <li><a href=\"#21随机挖-mask-的完形填空题是有隐患的\" id=\"markdown-toc-21随机挖-mask-的完形填空题是有隐患的\">2.1、随机挖 MASK 的完形填空题是有隐患的</a></li>\n <li><a href=\"#22nsp-任务有必要吗\" id=\"markdown-toc-22nsp-任务有必要吗\">2.2、NSP 任务有必要吗?</a></li>\n <li><a href=\"#23针对两个或以上词组成的连续词的词义被丢失\" id=\"markdown-toc-23针对两个或以上词组成的连续词的词义被丢失\">2.3、针对两个或以上词组成的连续词的词义被丢失</a></li>\n <li><a href=\"#24需要的算力高\" id=\"markdown-toc-24需要的算力高\">2.4、需要的算力高</a></li>\n <li><a href=\"#25需要的模型大\" id=\"markdown-toc-25需要的模型大\">2.5、需要的模型大</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#四一些关于-bert-的问题\" id=\"markdown-toc-四一些关于-bert-的问题\">四、一些关于 BERT 的问题</a> <ul>\n <li><a href=\"#1bert-模型的所谓双向与-bilstm-的双向是啥区别\" id=\"markdown-toc-1bert-模型的所谓双向与-bilstm-的双向是啥区别\">1、BERT 模型的所谓「双向」与 BiLSTM 的「双向」是啥区别?</a></li>\n <li><a href=\"#2为什么-bert-可以比-rnn-更好地并行化\" id=\"markdown-toc-2为什么-bert-可以比-rnn-更好地并行化\">2、为什么 BERT 可以比 RNN 更好地并行化</a></li>\n </ul>\n </li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<h3 id=\"一关于-bert-的一些背景\">一、关于 BERT 的一些背景</h3>\n\n<p>2018 年 Google 发布 BERT 后迅速在 NLP 领域引起广泛关注。BERT(Bidirectional Encoder Representations from Transformers)是一种自然语言处理(NLP)的深度学习模型,它可以进行语言模型预测、序列标注和问答等任务。BERT 采用双向的 Transformer 编码器架构,使用了大量的数据和计算资源进行训练,因此具有较强的泛化能力。</p>\n\n<p>BERT 的训练方法是通过让模型对给定的输入文本进行自监督学习,即使用未标记的语料进行训练。BERT 可以在很多 NLP 任务中获得较好的性能,并且由于其双向的编码方式,能够更好地理解语境信息。</p>\n\n<p>BERT 的训练需要大量的计算资源,因此它常常被用来作为解决 NLP 问题的预训练模型,可以用来初始化其他模型的权重,使得这些模型能够更快速地收敛。</p>\n\n<h3 id=\"二开始一个-bert-的动手小试验\">二、开始一个 BERT 的动手小试验</h3>\n\n<p>为了让 conda 使用 Python 3.7,你可以按照这些步骤来操作。</p>\n\n<h4 id=\"1安装-anaconda-来为部署-bert-做环境准备\">1、安装 Anaconda 来为部署 BERT 做环境准备</h4>\n\n<p>先了解几个概念:Anaconda 是一个软件包管理系统,其中包含了 conda 和许多其他的工具。Conda 是 Anaconda 中的一个组件,用于安装和管理软件包。\n我们需要用 conda 创建一个环境,在这个环境里去启用我们想要使用的 BERT 所需要的各种依赖。</p>\n\n<p>更新 conda 到最新版本:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda update <span class=\"nt\">-n</span> base conda\n</code></pre></div></div>\n\n<p>使用 Python 3.7 创建一个新的环境:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda create <span class=\"nt\">-n</span> py37 <span class=\"nv\">python</span><span class=\"o\">=</span>3.7\n</code></pre></div></div>\n\n<p>激活这个新环境:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda activate py37\n</code></pre></div></div>\n\n<p>验证正在使用的是正确版本的 Python</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python <span class=\"nt\">--version</span>\n</code></pre></div></div>\n\n<p>另外你可能还会用到的 conda 命令有:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 你之后一定会需要 deactivate 一个环境,命令如下:</span>\nconda deactivate py37\n\n<span class=\"c\"># 查看 conda 当前安装的所有库</span>\nconda list\n</code></pre></div></div>\n\n<h4 id=\"2安装-bert-所需要的各种依赖\">2、安装 BERT 所需要的各种依赖</h4>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda <span class=\"nb\">install </span><span class=\"nv\">tensorflow</span><span class=\"o\">==</span>1.14.0\n</code></pre></div></div>\n\n<p>验证 tensorflow 是否安装正确:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">tensorflow</span> <span class=\"k\">as</span> <span class=\"n\">tf</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">__version__</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"3下载一个预训练pre-train过的-bert-模型\">3、下载一个预训练(Pre-Train)过的 BERT 模型</h4>\n\n<p>官方的模型在这里浏览:https://github.com/google-research/bert#pre-trained-models</p>\n\n<p>也有一些中文的模型,以下是 ChatGPT 推荐的三个:</p>\n\n<ul>\n <li>BERT-Base, Chinese:这是 Google 官方提供的中文 BERT 模型,在中文 NLP 任务中表现良好。你可以从 这里下载这个模型。</li>\n <li>ERNIE:这是由中科院自然语言所提供的中文 BERT 模型,包含了额外的语义信息。你可以从 这里下载这个模型。</li>\n <li>RoBERTa-wwm-ext:这是由清华大学自然语言处理实验室提供的中文 BERT 模型,在多种中文 NLP 任务中表现良好。你可以从 这里下载这个模型。</li>\n</ul>\n\n<p>4、安装 BERT 的服务端和客户端</p>\n\n<p>这里我们使用 bert-as-service,bert-as-service 是一种将 BERT 模型部署为服务的方式。该工具使用 TensorFlow Serving 来运行 BERT 模型,并允许通过 REST API 进行调用。根据 bert-as-service 的文档,它已经在 TensorFlow 1.14.0 上测试过。</p>\n\n<p>在你激活的环境里,安装 <code class=\"language-plaintext highlighter-rouge\">bert-as-service</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 安装服务端和客户端</span>\n<span class=\"c\"># 更多关于 bert-serving-server 的信息可以参考:https://bert-serving.readthedocs.io/en/latest/index.html</span>\nconda <span class=\"nb\">install </span>bert-serving-server bert-serving-client \n验证 bert-as-service 是否安装成功\nbert-serving-start <span class=\"nt\">-h</span>\n</code></pre></div></div>\n\n<h4 id=\"5启动-bert-服务端\">5、启动 BERT 服务端</h4>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 命令行下启动BERT服务</span>\n<span class=\"c\"># -num_worker 表示启动几个worker服务,即可以处理几个并发请求,超过这个数字的请求将会在LBS(负载均衡器)中排队等待</span>\nbert-serving-start <span class=\"nt\">-model_dir</span> /模型/的/绝对/路径 <span class=\"nt\">-num_worker</span><span class=\"o\">=</span>4\n</code></pre></div></div>\n\n<h4 id=\"6在-pycharm-中使用-conda-的环境\">6、在 PyCharm 中使用 Conda 的环境</h4>\n\n<p>在 PyCharm 中启用 Interpreter 为 Anaconda,macOS 上具体地是在「Preference - Project - Python Interpreter - Add Interpreter - Add Local Interpreter - Conda Environment」。</p>\n\n<p>接下来还有一项重要的步骤就是选择该 project 要加载包文件的路径。如果不进行这一步,那该 project 还是从系统环境变量中的路径来搜索你要加载的包,这样在你用 Anaconda 新建的这个环境中所特有的包就会出现无法加载的问题。单击菜单栏 Run 选择 Edit Configuration。在Environment variables中添加一个新的 Path。新的路径为你用 Anaconda 新建的环境的文件夹中的<code class=\"language-plaintext highlighter-rouge\">「/Users/captain/opt/anaconda3/bin/python」</code>。</p>\n\n<p>配置 PyCharm 这里参考:https://docs.anaconda.com/anaconda/user-guide/tasks/pycharm/</p>\n\n<h4 id=\"7编写程序实现-bert-客户端\">7、编写程序实现 BERT 客户端</h4>\n\n<p>这里有一些客户端例子可以参考:https://blog.csdn.net/qq_18256855/article/details/123860126</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">bert_serving.client</span> <span class=\"kn\">import</span> <span class=\"n\">BertClient</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n\n<span class=\"c1\"># 定义类\n</span><span class=\"k\">class</span> <span class=\"nc\">BertModel</span><span class=\"p\">:</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span> <span class=\"o\">=</span> <span class=\"n\">BertClient</span><span class=\"p\">(</span><span class=\"n\">ip</span><span class=\"o\">=</span><span class=\"s\">'127.0.0.1'</span><span class=\"p\">,</span> <span class=\"n\">port</span><span class=\"o\">=</span><span class=\"mi\">5555</span><span class=\"p\">,</span> <span class=\"n\">port_out</span><span class=\"o\">=</span><span class=\"mi\">5556</span><span class=\"p\">)</span> <span class=\"c1\"># 创建客户端对象\n</span> <span class=\"c1\"># 注意:可以参考API,查看其它参数的设置\n</span> <span class=\"c1\"># 127.0.0.1 表示本机IP,也可以用localhost\n</span> <span class=\"k\">except</span><span class=\"p\">:</span>\n <span class=\"k\">raise</span> <span class=\"nb\">Exception</span><span class=\"p\">(</span><span class=\"s\">\"cannot create BertClient\"</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">close_bert</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">()</span> <span class=\"c1\"># 关闭服务\n</span>\n <span class=\"k\">def</span> <span class=\"nf\">sentence_embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">):</span>\n <span class=\"s\">'''对输入文本进行embedding\n Args:\n text: str, 输入文本\n Returns:\n text_vector: float, 返回一个列表,包含text的embedding编码值\n '''</span>\n <span class=\"n\">text_vector</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span><span class=\"p\">.</span><span class=\"n\">encode</span><span class=\"p\">([</span><span class=\"n\">text</span><span class=\"p\">])[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n <span class=\"k\">return</span> <span class=\"n\">text_vector</span> <span class=\"c1\"># 获取输出结果\n</span>\n <span class=\"k\">def</span> <span class=\"nf\">caculate_similarity</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">vec_1</span><span class=\"p\">,</span> <span class=\"n\">vec_2</span><span class=\"p\">):</span>\n <span class=\"s\">'''根据两个语句的vector,计算它们的相似性\n Args:\n vec_1: float, 语句1的vector\n vec_2: float, 语句2的vector\n Returns:\n sim_value: float, 返回相似性的计算值\n '''</span>\n <span class=\"c1\"># 根据cosine的计算公式\n</span> <span class=\"n\">v1</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mat</span><span class=\"p\">(</span><span class=\"n\">vec_1</span><span class=\"p\">)</span>\n <span class=\"n\">v2</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mat</span><span class=\"p\">(</span><span class=\"n\">vec_2</span><span class=\"p\">)</span>\n <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">v1</span> <span class=\"o\">*</span> <span class=\"n\">v2</span><span class=\"p\">.</span><span class=\"n\">T</span><span class=\"p\">)</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">norm</span><span class=\"p\">(</span><span class=\"n\">v1</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">norm</span><span class=\"p\">(</span><span class=\"n\">v2</span><span class=\"p\">)</span>\n <span class=\"n\">cosine</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">/</span> <span class=\"n\">b</span>\n <span class=\"k\">return</span> <span class=\"n\">cosine</span>\n\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">\"__main__\"</span><span class=\"p\">:</span>\n <span class=\"c1\"># 创建bert对象\n</span> <span class=\"n\">bert</span> <span class=\"o\">=</span> <span class=\"n\">BertModel</span><span class=\"p\">()</span>\n <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"c1\"># --- 输入语句 ----\n</span> <span class=\"n\">input_a</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s\">'请输入语句1: '</span><span class=\"p\">)</span>\n\n <span class=\"k\">if</span> <span class=\"n\">input_a</span> <span class=\"o\">==</span> <span class=\"s\">\"N\"</span> <span class=\"ow\">or</span> <span class=\"n\">input_a</span> <span class=\"o\">==</span> <span class=\"s\">\"n\"</span><span class=\"p\">:</span>\n <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">close_bert</span><span class=\"p\">()</span> <span class=\"c1\"># 关闭服务\n</span> <span class=\"k\">break</span>\n\n <span class=\"n\">input_b</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s\">'请输入语句2: '</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># --- 对输入语句进行embedding ---\n</span>\n <span class=\"n\">a_vec</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">sentence_embedding</span><span class=\"p\">(</span><span class=\"n\">input_a</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'a_vec shape : '</span><span class=\"p\">,</span> <span class=\"n\">a_vec</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n\n <span class=\"n\">b_vec</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">sentence_embedding</span><span class=\"p\">(</span><span class=\"n\">input_b</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'b_vec shape : '</span><span class=\"p\">,</span> <span class=\"n\">b_vec</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 计算两个语句的相似性\n</span> <span class=\"n\">cos</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">caculate_similarity</span><span class=\"p\">(</span><span class=\"n\">a_vec</span><span class=\"p\">,</span> <span class=\"n\">b_vec</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'cosine value : '</span><span class=\"p\">,</span> <span class=\"n\">cos</span><span class=\"p\">)</span>\n\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'</span><span class=\"se\">\\n\\n</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 如果相似性值大于0.85,则输出相似,否则,输出不同\n</span> <span class=\"k\">if</span> <span class=\"n\">cos</span> <span class=\"o\">></span> <span class=\"mf\">0.85</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"2个语句的含义相似\"</span><span class=\"p\">)</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"不相似\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在使用 <code class=\"language-plaintext highlighter-rouge\">bert-serving-client</code> 连接 <code class=\"language-plaintext highlighter-rouge\">bert-serving-server</code> 时,你需要确保 <code class=\"language-plaintext highlighter-rouge\">bert-serving-server</code> 使用的模型和 <code class=\"language-plaintext highlighter-rouge\">bert-serving-client</code> 使用的模型是匹配的,否则会出现错误。</p>\n\n<p>程序正常运行后,将要求你输入两句话,然后 BERT 计算两句话的相似性。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>请输入语句1: \n请输入语句2: \n</code></pre></div></div>\n\n<p>两句输入好确认后,得到如下形式的结果:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>a_vec shape : (768,)\nb_vec shape : (768,)\ncosine value : 0.8691698561422959\n</code></pre></div></div>\n\n<p>其实这个小试验蛮没意思的,而且准确性也比较令人质疑。</p>\n\n<h3 id=\"三bert-模型的优劣势及其原因\">三、BERT 模型的优劣势及其原因</h3>\n\n<p>论文地址:<a href=\"https://arxiv.org/abs/1810.04805\">《BERT: Pre-Training of Deep Bidirectional Transformers for Language Understanding》</a> 。</p>\n\n<h4 id=\"1bert-的优势是很明显的\">1、BERT 的优势是很明显的</h4>\n\n<p>复旦大学的邱锡鹏教授层评价 BERT 的「里程碑意义」在于:</p>\n\n<blockquote>\n <p>证明了一个非常深的模型可以显著提高 NLP 任务的准确率,而这个模型可以从无标记数据集中预训练得到。</p>\n</blockquote>\n\n<h5 id=\"11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\">1.1、MLM 和 NSP 预训练能够捕捉到自然语言中的各种复杂细节</h5>\n\n<p>因为 BERT 采用了双向的自注意力机制,这里的「双向」意味着 BERT 模型可以同时利用输入文本的前后文信息来预测下一个词是什么、下一句是什么。这样 BERT 模型就可以捕捉到自然语言中的各种隐藏的细节,比如语义关系、语法结构、语义暗示等等。</p>\n\n<p>具体地,BERT 采用了 Masked Language Model(MLM)来做「下一个词是什么」的预训练,采用了 Next Sentence Prediction(NSP)来做「下一句是什么」的预训练。MLM 的方式其实就很像英语考试里的「完形填空」,而 NSP 的方式,就像整句的完形填空。</p>\n\n<h5 id=\"12识别并专注于较重要的部分进行文本处理\">1.2、识别并专注于较重要的部分进行文本处理</h5>\n\n<p>这要得益于因为 BERT 采用了自注意力机制。自注意力机制,通过计算输入单元的权重值,来确定在一个输入序列中哪些输入单元是重要的。具体地,一个输入单元与其他单元的相似性越高,按照我们自然语言的逻辑,那么这部分是在被重复、强调、翻来覆去用不同的方式在解释,那么这部分就是重要的,权重值就更高。</p>\n\n<h5 id=\"13快速构建针对具体任务的-nlp-系统\">1.3、快速构建针对具体任务的 NLP 系统</h5>\n\n<p>因为 BERT 采用了预训练模型,能够在没有监督标注数据的情况下从大量文本中学习语言模型。因为我们认为上下文信息本身就能推测出某个词,所以大量的文本数据本身就是一种「自带标注」的数据,所以 BERT 能够无监督学习。</p>\n\n<h4 id=\"2bert-模型的劣势及其原因\">2、BERT 模型的劣势及其原因</h4>\n\n<h5 id=\"21随机挖-mask-的完形填空题是有隐患的\">2.1、随机挖 MASK 的完形填空题是有隐患的</h5>\n\n<p>对于上面提到的 MLM、NSP 方法做预训练,那么问题也就显而易见了,如果我们挖掉的一组 MASK 完形填空词,是强关联的(非条件独立),那么这一组词的预测就都会出现问题。</p>\n\n<h5 id=\"22nsp-任务有必要吗\">2.2、NSP 任务有必要吗?</h5>\n\n<p>论文《Crosslingual language model pretraining》中提到 BERT 的 NSP 可能是非必要的,针对这个问题,后续出现的模型都移除了 NSP 任务,比如 RoBERTa、spanBERT、ALBERT。</p>\n\n<h5 id=\"23针对两个或以上词组成的连续词的词义被丢失\">2.3、针对两个或以上词组成的连续词的词义被丢失</h5>\n\n<p>比如 cutting-edge,MLM 的方式可能会割裂这两个子词的相关性,导致模型丢失这个词的词义,针对这个问题 Google 后来发表了 BERT-WWM,WWM 即 Whole Word Masking,从字面就能理解针对的问题。哈尔滨工业大学的科大讯飞联合实验室后来推出了 Chinese-BERT-WWM 专门针对中文解决了这个问题。</p>\n\n<h5 id=\"24需要的算力高\">2.4、需要的算力高</h5>\n\n<p>算力高,自然需要的计算成本运行更高。不过算力成本高这种问题总有办法优化,通常来说不是模型本身所处理问题的局限性和先决条件的局限性(比如依赖大量人工工作)就非常好了。</p>\n\n<h5 id=\"25需要的模型大\">2.5、需要的模型大</h5>\n\n<p>模型大,自然存储成本也就高了。这也类似于上一点,而且算力、存储成本高,可以在大型应用中把成本均摊下来,比如 BERT 如果支持的某个 AGI 应用得到广泛普及。</p>\n\n<h3 id=\"四一些关于-bert-的问题\">四、一些关于 BERT 的问题</h3>\n\n<h4 id=\"1bert-模型的所谓双向与-bilstm-的双向是啥区别\">1、BERT 模型的所谓「双向」与 BiLSTM 的「双向」是啥区别?</h4>\n\n<p>BiLSTM 是把句子再倒序一遍,而 BERT 的双向是指在 Encoder 的自注意力机制下编码一个 token 时「同时利用上下文」的 token。</p>\n\n<h4 id=\"2为什么-bert-可以比-rnn-更好地并行化\">2、为什么 BERT 可以比 RNN 更好地并行化</h4>\n\n<p>RNN 因为有时序概念,即后面的特征计算,依赖于前面计算的结果,所以就形成了循环(Recurrent)。而 BERT 采用了自注意力机制则没有时序概念,每个词特征都依赖其上下文独立计算,因此更容易并行化。</p>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>https://arxiv.org/abs/1810.04805</li>\n <li>https://github.com/google-research/bert</li>\n <li>https://github.com/ymcui/Chinese-BERT-wwm</li>\n <li>https://zhuanlan.zhihu.com/p/195723105</li>\n <li>https://www.jiqizhixin.com/articles/2018-10-24-13</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"生成式AI":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Generative AI":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"game":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Gen-AI":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"深度学习":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Deep Learning":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Artificial Neural Network":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"机器学习":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Machine Learning":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"ML":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"ANN":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"CNN":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"RNN":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"循环神经网络":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"人工神经网络":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"Artificial Intelligence":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"LSTM":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"长短时记忆":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"GPT-3":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"davinci":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"curie":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"ada":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"babbage":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"fine-tune":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"fine-tuning":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"精调":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"completion":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"prompting":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"训练":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"数据集":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"]},"posts":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>国家网信办《互联网信息服务深度合成管理规定》解读</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>国家网信办《互联网信息服务深度合成管理规定》解读</h2>\t\t\n\t<time datetime=\"2023-02-06T15:24:58+00:00\" class=\"by-line\">06 Feb 2023, 香港 | 麦克船长 | 总计 4347 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"国家互联网信息办公室中华人民共和国工业和信息化部中华人民共和国公安部-令-第12号\">国家互联网信息办公室、中华人民共和国工业和信息化部、中华人民共和国公安部 令 第12号</h2>\n\n<p>《互联网信息服务深度合成管理规定》已经2022年11月3日国家互联网信息办公室2022年第21次室务会议审议通过,并经工业和信息化部、公安部同意,现予公布,自2023年1月10日起施行。</p>\n\n<p>国家互联网信息办公室主任 庄荣文</p>\n\n<p>工业和信息化部部长 金壮龙</p>\n\n<p>公安部部长 王小洪</p>\n\n<p>2022年11月25日</p>\n\n<h2 id=\"互联网信息服务深度合成管理规定\">互联网信息服务深度合成管理规定</h2>\n\n<h3 id=\"第一章-总则\">第一章 总则</h3>\n\n<p>第一条 为了加强互联网信息服务深度合成管理,弘扬社会主义核心价值观,维护国家安全和社会公共利益,保护公民、法人和其他组织的合法权益,根据《中华人民共和国网络安全法》、《中华人民共和国数据安全法》、《中华人民共和国个人信息保护法》、《互联网信息服务管理办法》等法律、行政法规,制定本规定。</p>\n\n<p>第二条 在中华人民共和国境内应用深度合成技术提供互联网信息服务(以下简称深度合成服务),适用本规定。法律、行政法规另有规定的,依照其规定。</p>\n\n<p>第三条 国家网信部门负责统筹协调全国深度合成服务的治理和相关监督管理工作。国务院电信主管部门、公安部门依据各自职责负责深度合成服务的监督管理工作。</p>\n\n<p>地方网信部门负责统筹协调本行政区域内的深度合成服务的治理和相关监督管理工作。地方电信主管部门、公安部门依据各自职责负责本行政区域内的深度合成服务的监督管理工作。</p>\n\n<p>第四条 提供深度合成服务,应当遵守法律法规,尊重社会公德和伦理道德,坚持正确政治方向、舆论导向、价值取向,促进深度合成服务向上向善。</p>\n\n<p>第五条 鼓励相关行业组织加强行业自律,建立健全行业标准、行业准则和自律管理制度,督促指导深度合成服务提供者和技术支持者制定完善业务规范、依法开展业务和接受社会监督。</p>\n\n<h3 id=\"第二章-一般规定\">第二章 一般规定</h3>\n\n<p>第六条 任何组织和个人不得利用深度合成服务制作、复制、发布、传播法律、行政法规禁止的信息,不得利用深度合成服务从事危害国家安全和利益、损害国家形象、侵害社会公共利益、扰乱经济和社会秩序、侵犯他人合法权益等法律、行政法规禁止的活动。</p>\n\n<p><strong><u>深度合成服务提供者和使用者不得利用深度合成服务制作、复制、发布、传播虚假新闻信息。转载基于深度合成服务制作发布的新闻信息的,应当依法转载互联网新闻信息稿源单位发布的新闻信息</u>></strong>。</p>\n\n<p>第七条 深度合成服务提供者应当落实信息安全主体责任,建立健全用户注册、<strong><u>算法机制机理审核、科技伦理审查</u></strong>、信息发布审核、数据安全、个人信息保护、反电信网络诈骗、应急处置等管理制度,具有安全可控的技术保障措施。</p>\n\n<p>第八条 深度合成服务提供者应当制定和公开管理规则、平台公约,完善服务协议,依法依约履行管理责任,以显著方式提示深度合成服务技术支持者和使用者承担信息安全义务。</p>\n\n<p>第九条 深度合成服务提供者应当基于移动电话号码、身份证件号码、统一社会信用代码或者国家网络身份认证公共服务等方式,依法对深度合成服务使用者进行真实身份信息认证,<strong><u>不得向未进行真实身份信息认证的深度合成服务使用者提供信息发布服务</u></strong>。</p>\n\n<p>第十条 深度合成服务提供者应当加强深度合成内容管理,采取<strong><u>技术</u></strong>或者人工方式对深度合成服务使用者的输入数据和合成结果进行审核。</p>\n\n<p><strong><u>深度合成服务提供者应当建立健全用于识别违法和不良信息的特征库,完善入库标准、规则和程序,记录并留存相关网络日志</u></strong>。</p>\n\n<p>深度合成服务提供者发现违法和不良信息的,应当依法采取处置措施,保存有关记录,及时向网信部门和有关主管部门报告;对相关深度合成服务使用者依法依约采取警示、限制功能、暂停服务、关闭账号等处置措施。</p>\n\n<p>第十一条 <strong><u>深度合成服务提供者应当建立健全辟谣机制</u></strong>,发现利用深度合成服务制作、复制、发布、传播虚假信息的,应当及时采取辟谣措施,保存有关记录,并向网信部门和有关主管部门报告。</p>\n\n<p>第十二条 深度合成服务提供者应当设置便捷的用户申诉和公众投诉、举报入口,公布处理流程和反馈时限,及时受理、处理和反馈处理结果。</p>\n\n<p>第十三条 互联网应用商店等应用程序分发平台应当落实上架审核、日常管理、应急处置等安全管理责任,核验深度合成类应用程序的安全评估、备案等情况;对违反国家有关规定的,应当及时采取不予上架、警示、暂停服务或者下架等处置措施。</p>\n\n<blockquote>\n <p>麦克船长解读:第二章整体就是告诉我们一句话,所有开发、分发深度合成产品的组织或个人,包括生成式 AI 的 SaaS 服务商(比如百度文心大模型)、to C 型应用平台/商店(比如华为应用商店/小米应用商店/微信小程序等)等,必须实现 Moderation 能力。</p>\n</blockquote>\n\n<h3 id=\"第三章-数据和技术管理规范\">第三章 数据和技术管理规范</h3>\n\n<p>第十四条 深度合成服务提供者和技术支持者应当加强训练数据管理,采取必要措施保障训练数据安全;训练数据包含个人信息的,应当遵守个人信息保护的有关规定。</p>\n\n<p><strong><u>深度合成服务提供者和技术支持者提供人脸、人声等生物识别信息编辑功能的,应当提示深度合成服务使用者依法告知被编辑的个人,并取得其单独同意</u></strong>。</p>\n\n<p>第十五条 深度合成服务提供者和技术支持者应当加强技术管理,<strong><u>定期审核、评估、验证生成合成类算法机制机理</u></strong>。</p>\n\n<p>深度合成服务提供者和技术支持者提供具有以下功能的模型、模板等工具的,<strong><u>应当依法自行或者委托专业机构开展安全评估</u></strong>:</p>\n\n<p>(一)生成或者编辑人脸、人声等生物识别信息的;</p>\n\n<p>(二)生成或者编辑可能涉及国家安全、国家形象、国家利益和社会公共利益的特殊物体、场景等非生物识别信息的。</p>\n\n<p>第十六条 深度合成服务提供者对使用其服务生成或者编辑的信息内容,应当采取技术措施添加不影响用户使用的标识,并依照法律、行政法规和国家有关规定保存日志信息。</p>\n\n<p>第十七条 深度合成服务提供者提供以下深度合成服务,可能导致公众混淆或者误认的,应当<strong><u>在生成或者编辑的信息内容的合理位置、区域进行显著标识,向公众提示深度合成情况</u></strong>:</p>\n\n<p>(一)智能对话、智能写作等模拟自然人进行文本的生成或者编辑服务;</p>\n\n<p>(二)合成人声、仿声等语音生成或者显著改变个人身份特征的编辑服务;</p>\n\n<p>(三)人脸生成、人脸替换、人脸操控、姿态操控等人物图像、视频生成或者显著改变个人身份特征的编辑服务;</p>\n\n<p>(四)沉浸式拟真场景等生成或者编辑服务;</p>\n\n<p>(五)其他具有生成或者显著改变信息内容功能的服务。</p>\n\n<p>深度合成服务提供者提供前款规定之外的深度合成服务的,<strong><u>应当提供显著标识功能,并提示深度合成服务使用者可以进行显著标识</u></strong>。</p>\n\n<p>第十八条 任何组织和个人<strong><u>不得采用技术手段删除、篡改、隐匿本规定第十六条和第十七条规定的深度合成标识</u></strong>。</p>\n\n<blockquote>\n <p>麦克船长解读:1)必须有评估、验证合成算法机制;2)必须要有明确标识告诉使用者;3)生成类服务的范围,在第十七条的第五款里,用「其他」变成了一个什么都能装的筐。</p>\n</blockquote>\n\n<h3 id=\"第四章-监督检查与法律责任\">第四章 监督检查与法律责任</h3>\n\n<p>第十九条 <strong><u>具有舆论属性或者社会动员能力的深度合成服务提供者,应当按照《互联网信息服务算法推荐管理规定》履行备案和变更、注销备案手续</u></strong>。</p>\n\n<p>深度合成服务技术支持者应当参照前款规定履行备案和变更、注销备案手续。</p>\n\n<p>完成备案的深度合成服务提供者和技术支持者应当在其对外提供服务的网站、应用程序等的<strong><u>显著位置标明其备案编号并提供公示信息链接</u></strong>。</p>\n\n<blockquote>\n <p>麦克船长解读:有媒体舆论属性的产品(比如社交社区、直播短视频、新闻媒体类等),一定要按照《互联网信息服务算法推荐管理规定》备案,并在 APP、网站上公示备案编号并提供链接(类似之前的域名备案)。</p>\n</blockquote>\n\n<p>第二十条 深度合成服务提供者开发上线具有舆论属性或者社会动员能力的新产品、新应用、新功能的,应当按照国家有关规定开展安全评估。</p>\n\n<blockquote>\n <p>麦克船长解读:上述提到的产品迭代新能力时,也要再次安全评估。</p>\n</blockquote>\n\n<p>第二十一条 网信部门和电信主管部门、公安部门依据职责对深度合成服务开展监督检查。深度合成服务提供者和技术支持者应当依法予以配合,并提供必要的技术、数据等支持和协助。</p>\n\n<p>网信部门和有关主管部门发现深度合成服务存在较大信息安全风险的,可以按照职责依法要求深度合成服务提供者和技术支持者采取暂停信息更新、用户账号注册或者其他相关服务等措施。深度合成服务提供者和技术支持者应当按照要求采取措施,进行整改,消除隐患。</p>\n\n<blockquote>\n <p>麦克船长解读:网信部门、电信部门、公安部门都可以监管,都要配合。监管的整改可能会配合停止信息更新、停止账号注册等。这就跟食品生产企业的整改监管非常类似了,生产内容一样要被这样监管,有了合成能力本质上就是出现了内容工厂了。</p>\n</blockquote>\n\n<p>第二十二条 深度合成服务提供者和技术支持者违反本规定的,依照有关法律、行政法规的规定处罚;造成严重后果的,依法从重处罚。</p>\n\n<p>构成违反治安管理行为的,由公安机关依法给予治安管理处罚;构成犯罪的,依法追究刑事责任。</p>\n\n<h3 id=\"第五章-附则\">第五章 附则</h3>\n\n<p>第二十三条 本规定中下列用语的含义:</p>\n\n<p>深度合成技术,是指利用深度学习、虚拟现实等生成合成类算法制作文本、图像、音频、视频、虚拟场景等网络信息的技术,包括但不限于:</p>\n\n<p>(一)篇章生成、文本风格转换、问答对话等生成或者编辑文本内容的技术;</p>\n\n<p>(二)文本转语音、语音转换、语音属性编辑等生成或者编辑语音内容的技术;</p>\n\n<p>(三)音乐生成、场景声编辑等生成或者编辑非语音内容的技术;</p>\n\n<p>(四)人脸生成、人脸替换、人物属性编辑、人脸操控、姿态操控等生成或者编辑图像、视频内容中生物特征的技术;</p>\n\n<p>(五)图像生成、图像增强、图像修复等生成或者编辑图像、视频内容中非生物特征的技术;</p>\n\n<p>(六)三维重建、数字仿真等生成或者编辑数字人物、虚拟场景的技术。</p>\n\n<p>深度合成服务提供者,是指提供深度合成服务的组织、个人。</p>\n\n<p>深度合成服务技术支持者,是指为深度合成服务提供技术支持的组织、个人。</p>\n\n<p>深度合成服务使用者,是指使用深度合成服务制作、复制、发布、传播信息的组织、个人。</p>\n\n<p>训练数据,是指被用于训练机器学习模型的标注或者基准数据集。</p>\n\n<p>沉浸式拟真场景,是指应用深度合成技术生成或者编辑的、可供参与者体验或者互动的、具有高度真实感的虚拟场景。</p>\n\n<p>第二十四条 深度合成服务提供者和技术支持者从事网络出版服务、网络文化活动和网络视听节目服务的,应当同时符合新闻出版、文化和旅游、广播电视主管部门的规定。</p>\n\n<p>第二十五条 本规定自2023年1月10日起施行。</p>\n\n<blockquote>\n <p>麦克船长解读:不仅限于 AIGC,虚拟现实生成也被该规定覆盖。一些可能会想到的问题如下。</p>\n <ol>\n <li>比如我只用 AI 改改文章风格不用被监管吧?否,也按本规定监管。</li>\n <li>艺术类,非写实类的图像生成,不用被监管吧?否,也被本规定监管,不是只有写实的内容才有可能不合规。</li>\n <li>用 AI 构建 3D 模型,主要用于装饰、装修、装潢的,不用被监管吧?否,这个能力有了,就不限于生成范围了,会有涉及监管的应用,所以也要被监管。</li>\n </ol>\n</blockquote>\n\n<h2 id=\"参考\">参考:</h2>\n\n<ul>\n <li>http://www.cac.gov.cn/2022-12/11/c_1672221949354811.htm</li>\n <li>https://tisi.org/14419</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</h2>\t\t\n\t<time datetime=\"2023-01-08T18:13:09+00:00\" class=\"by-line\">08 Jan 2023, 杭州 | 麦克船长 | 总计 40590 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-1.jpeg\" alt=\"image\" /></p>\n\n<p>原文链接:<a href=\"https://zhuanlan.zhihu.com/p/597586623\">https://zhuanlan.zhihu.com/p/597586623</a></p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一潮流之巅nlp-研究范式的转换\" id=\"markdown-toc-一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</a> <ul>\n <li><a href=\"#1范式转换-10从深度学习到两阶段预训练模型\" id=\"markdown-toc-1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</a> <ul>\n <li><a href=\"#11影响一中间任务的消亡\" id=\"markdown-toc-11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</a></li>\n <li><a href=\"#12影响二不同研究方向技术路线的统一\" id=\"markdown-toc-12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</a></li>\n </ul>\n </li>\n <li><a href=\"#2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\" id=\"markdown-toc-2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</a> <ul>\n <li><a href=\"#21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\" id=\"markdown-toc-21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</a></li>\n </ul>\n </li>\n <li><a href=\"#影响一让-llm-适配人的新型交互接口\" id=\"markdown-toc-影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</a></li>\n <li><a href=\"#影响二很多-nlp-子领域不再具备独立研究价值\" id=\"markdown-toc-影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</a></li>\n <li><a href=\"#影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\" id=\"markdown-toc-影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</a></li>\n </ul>\n </li>\n <li><a href=\"#二学习者从无尽数据到海量知识\" id=\"markdown-toc-二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</a> <ul>\n <li><a href=\"#1求知之路llm-学到了什么知识\" id=\"markdown-toc-1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</a></li>\n <li><a href=\"#2记忆之地llm-如何存取知识\" id=\"markdown-toc-2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</a></li>\n <li><a href=\"#3知识涂改液如何修正-llm-里存储的知识\" id=\"markdown-toc-3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</a></li>\n </ul>\n </li>\n <li><a href=\"#三规模效应当-llm-越来越大时会发生什么\" id=\"markdown-toc-三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</a></li>\n <li><a href=\"#四人机接口从-in-context-learning-到-instruct-理解\" id=\"markdown-toc-四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</a> <ul>\n <li><a href=\"#1神秘的-in-context-learning\" id=\"markdown-toc-1神秘的-in-context-learning\">1、神秘的 In Context Learning</a></li>\n <li><a href=\"#2神奇的-instruct-理解\" id=\"markdown-toc-2神奇的-instruct-理解\">2、神奇的 Instruct 理解</a></li>\n <li><a href=\"#3in-context-learning-和-instruct-的联系\" id=\"markdown-toc-3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</a></li>\n </ul>\n </li>\n <li><a href=\"#五智慧之光如何增强-llm-的推理能力\" id=\"markdown-toc-五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</a> <ul>\n <li><a href=\"#1基于-prompt-的方法\" id=\"markdown-toc-1基于-prompt-的方法\">1、基于 Prompt 的方法</a></li>\n <li><a href=\"#2代码预训练增强-llm-推理能力\" id=\"markdown-toc-2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</a></li>\n <li><a href=\"#3关于-llm-推理能力的思考\" id=\"markdown-toc-3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</a></li>\n </ul>\n </li>\n <li><a href=\"#六未来之路llm-研究趋势及值得研究的重点方向\" id=\"markdown-toc-六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</a> <ul>\n <li><a href=\"#探索-llm-模型的规模天花板\" id=\"markdown-toc-探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</a></li>\n <li><a href=\"#增强-llm-的复杂推理能力\" id=\"markdown-toc-增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</a></li>\n <li><a href=\"#llm-纳入-nlp-之外更多其它研究领域\" id=\"markdown-toc-llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</a></li>\n <li><a href=\"#更易用的人和-llm-的交互接口\" id=\"markdown-toc-更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</a></li>\n <li><a href=\"#建设高难度的综合任务评测数据集\" id=\"markdown-toc-建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</a></li>\n <li><a href=\"#高质量数据工程\" id=\"markdown-toc-高质量数据工程\">高质量数据工程</a></li>\n <li><a href=\"#超大-llm-模型-transformer-的稀疏化\" id=\"markdown-toc-超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</a></li>\n </ul>\n </li>\n <li><a href=\"#七取经之路复刻-chatgpt-时要注意些什么\" id=\"markdown-toc-七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</a></li>\n <li><a href=\"#八chatgpt为什么是-openai\" id=\"markdown-toc-八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</a></li>\n</ul>\n\n<p>ChatGPT 出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型(LLM,Large Language Model)效果能好成这样;惊醒是顿悟到我们对LLM的认知及发展理念,距离世界最先进的想法,差得有点远。我属于既惊喜又惊醒的那一批,也是典型的中国人,中国人善于自我反思,于是开始反思,而这篇文章正是反思的结果。</p>\n\n<p>实话实说,国内在 LLM 模型相关技术方面,此刻,距离最先进技术的差距进一步加大了。技术领先或技术差距这事情,我觉得要动态地以发展的眼光来看。<strong>在 Bert 出现之后的一到两年间,其实国内在这块的技术追赶速度还是很快的,也提出了一些很好的改进模型,差距拉开的分水岭应该是在 GPT 3.0 出来之后,也就是 2020 年年中左右</strong>。在当时,其实只有很少的人觉察到:GPT 3.0 它不仅仅是一项具体的技术,其实体现的是 LLM 应该往何处去的一个发展理念。自此之后,差距拉得越来越远,ChatGPT 只是这种发展理念差异的一个自然结果。所以,我个人认为,抛开是否有财力做超大型 LLM 这个因素,如果单从技术角度看,差距主要来自于对 LLM 的认知以及未来应往何处去的发展理念的不同。</p>\n\n<p>国内被国外技术甩得越来越远,这个是事实,不承认也不行。前阵子网上很多人担忧说国内 AI 现在处于「危急存亡之秋」,我觉得倒也不至于这么严重。君不见,这个世界上,具备这么超前眼光的只有 OpenAI 一家吗?<strong>包括 Google 在内,其实对于 LLM 发展理念的理解,明显都落后 OpenAI 一个身位。现实是 OpenAI 表现过于优秀,把所有人都甩开了,不仅仅是国内</strong>。</p>\n\n<p>我觉得,OpenAI 对 LLM 在理念及相关技术方面,领先国外的 Google、DeepMind 大约半年到一年的时间,领先国内大概两年左右的时间。在 LLM 这个事情上,感觉梯队很明显,Google 应该是排在第二位,最能体现 Google 技术眼光的是 PaLM 和 Pathways,推出时间大概在 22 年 2 月到 4 月间,同一时期,OpenAI 推出的却是 InstructGPT,从这里就可以看出 Google 和 OpenAI 的差距了,至于为何这么说,你看了我后面的正文后大概能理解。DeepMind 之前的重心一直在强化学习攻克游戏和 AI for science 这些方面,切入LLM 其实很晚,应该是21 年才开始重视这个方向,目前也处于追赶状态。Meta 就更不用说了,重心一直不在 LLM 上,目前感觉也发力开始追赶。这还是目前做得最好的一批机构,尚且如此,更何况国内呢?我觉得情有可原。至于 OpenAI 关于 LLM 的理念是什么,我在本文的最后一部分,会谈谈我的认知。</p>\n\n<p>本文梳理自 GPT 3.0 出现之后的主流 LLM 技术,能够让您对 LLM 领域的技术脉络,LLM 技术发展过程中出现过的不同发展理念,乃至未来可能的发展趋势,有比较清晰的认知。当然,很多地方讲的内容是我个人看法,有很大的主观性,错漏难免,所以还请谨慎参考。</p>\n\n<p>本文试图回答下面一些问题:ChatGPT 是否带来了 NLP 乃至 AI 领域的研究范式转换?如果是,那会带来怎样的影响?LLM 从海量数据中学到了什么知识?LLM 又是如何存取这些知识的?随着LLM规模逐步增大,会带来什么影响?什么是 In Context Learning?为什么它是一项很神秘的技术?它和 Instruct 又是什么关系?LLM 具备推理能力吗?思维链 CoT 又是怎么做的?等等,相信看完,能让您对这些问题有一个答案。</p>\n\n<p>首先,在谈 LLM 技术现状前,先宏观地谈下我心目中的研究范式转换问题。这样,我们才能「先见森林,再见树木」,对具体技术为何会是如此变化有个更清晰的认知。</p>\n\n<h2 id=\"一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</h2>\n\n<p>如果我们把时间线往前拉得更长一些,回到 NLP 领域的深度学习时代,在更长时间窗口内观察技术变迁及其影响,可能会更容易看清其中的一些关键节点。我个人认为,在最近 10 年来NLP领域的技术发展过程中,可能存在两次大的研究范型转换。</p>\n\n<h3 id=\"1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在深度学习引入 NLP 领域(2013 年左右),到 GPT 3.0 出现之前(2020 年 5 月左右)。</p>\n\n<p>在 Bert 和 GPT 模型出现之前,NLP 领域流行的技术是深度学习模型,而 NLP 领域的深度学习,主要依托于以下几项关键技术:以大量的改进 LSTM 模型及少量的改进 CNN 模型作为典型的特征抽取器;以 Sequence to Sequence(或叫 encoder-decoder 亦可)+ Attention 作为各种具体任务典型的总体技术框架。</p>\n\n<p>在这些核心技术加持下,NLP 领域深度学习的主要研究目标,如果归纳一下,是如何有效增加模型层深或模型参数容量。就是说,怎么才能往 encoder 和 decoder 里不断叠加更深的 LSTM 或 CNN 层,来达成增加层深和模型容量的目标。这种努力,尽管确实不断增加了模型层深,但是从解决具体任务的效果角度看,总体而言,不算很成功,或者说和非深度学习方法相比,带来的优势不算大。</p>\n\n<p>深度学习之所以不够成功,我认为主要原因来自于两个方面:一方面是某个具体任务有限的训练数据总量。随着模型容量的增加,需要靠更大量的训练数据来支撑,否则即使你能把深度做起来,任务效果也做不上去。而在预训练模型出现之前,很明显这是 NLP 研究领域一个严重问题;另外一个方面是 LSTM/CNN 特征抽取器,表达能力不够强。意思是就算给你再多的数据也没用,因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因,阻碍了深度学习在 NLP 领域的成功突围。</p>\n\n<p>Bert / GPT 这两个预训练模型的出现,无论在学术研究角度看,还是工业应用角度来看,都代表了 NLP 领域的一个技术飞跃,并带来了整个领域研究范式的转换。这种范式转换带来的影响,体现在两个方面:首先,是部分 NLP 研究子领域的衰退乃至逐步消亡;其次,NLP 不同子领域的技术方法和技术框架日趋统一,在 Bert 出现后一年左右,技术栈基本收敛到两种技术模式中。关于这两点,我们分头来谈。</p>\n\n<h4 id=\"11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</h4>\n\n<p>NLP 是一个宏观研究领域的统称,里面有五花八门具体的子领域与子方向,如果仔细分析,从任务的性质角度,可以把这些任务分成两大类:一类可以叫做「中间任务」,一类可以称为「最终任务」。</p>\n\n<p>典型的中间任务包括:中文分词、词性标注、NER、句法分析、指代消解、语义 Parser 等,这类任务一般并不解决应用中的实际需求,大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的,比如几乎没有需求说,我要一个句法 Parser,把这个句子的句法分析树给用户看看,用户不需要看到这些NLP的中间阶段处理结果,他只关心某个具体任务你有没有干好。「最终任务」包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等,有很多。这类任务的特点是每个子领域都解决某个实际需求,任务结果基本能直接呈现给用户,比如用户确实存在给你一句英文,告诉他中文是什么的需求。</p>\n\n<p>按理说,「中间任务」就不应该出现,而之所以会存在,这是 NLP 技术发展水平不够高的一种体现。在技术发展早期阶段,因为当时的技术相对落后,很难一步做好有难度的最终任务。比如机器翻译,早期技术要做好机器翻译是很困难的,于是科研人员就把难题分而治之,分解成分词、词性标注、句法分析等各种中间阶段,先把每个中间阶段做好,然后再拼起来完成最终任务,这也是没办法的事情。</p>\n\n<p>但是自从 Bert/GPT 出现之后,其实就没有必要做这些中间任务了,因为通过大量数据的预训练,Bert/GPT 已经把这些中间任务作为语言学特征,吸收到了 Transformer 的参数里,此时我们完全可以端到端地直接解决那些最终任务,而无须对这种中间过程专门建模。这里可能争议最大的是中文分词,其实道理也是一样的,哪些字应该组成一个词,这个其实你不用管,让 LLM 自己当特征去学就行了,只要对于解决任务有帮助,它自然会去学该学的合理分词方式,也未必一定要和我们人类理解的分词规则相同。</p>\n\n<p>基于以上认知,其实在Bert/GPT一出现,你就应该得出这类NLP的中间阶段的任务,会逐步退出历史舞台这个结论。</p>\n\n<h4 id=\"12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</h4>\n\n<p>在说明具体影响前,我们先讨论下另外一种 NLP 任务划分方式,这对于理解后面内容有帮助。如果对「最终任务」进一步进行分类,又大致可以分为两大不同类型的任务:自然语言理解类任务和自然语言生成类任务。如果排除掉「中间任务」的话,典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等,这种任务本质上都是分类任务,就是说输入一个句子(文章),或者两个句子,模型参考所有输入内容,最后给出属于哪个类别的判断。自然语言生成也包含很多 NLP 研究子方向,比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本,对应地,模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上。</p>\n\n<p>自从 Bert/GPT 模型诞生后,出现了明显的技术统一趋向。首先,NLP 中不同的子领域,其特征抽取器都逐渐从 LSTM/CNN 统一到 Transformer 上。其实,自Bert公开后不久,就应该意识到,这必然会成为技术趋势。而且,目前 Transformer 不仅统一了 NLP 诸多领域,也正在逐步地替换图像处理各种任务中被广泛使用的 CNN 等其它模型的进程之中,类似的,多模态模型目前也基本都采用了 Transformer 模型。这种Transformer从NLP出发,攻城略地逐步统一AI越来越多领域的趋势,起始于 2020 年底出现的 Vision Transformer (ViT) ,之后蓬勃发展,到目前已大获成功,且其继续向更多领域拓展的势头会越来越迅猛。</p>\n\n<p>其次,大多数 NLP 子领域的研发模式切换到了两阶段模式:模型预训练阶段 + 应用微调(Fine-tuning)或应用 Zero/Few Shot Prompt 模式。更准确地说,NLP 各种任务其实收敛到了两个不同的预训练模型框架里:对于自然语言理解类任务,其技术体系统一到了以 Bert 为代表的「双向语言模型预训练 + 应用 Fine-tuning」模式;而对于自然语言生成类任务,其技术体系则统一到了以GPT 2.0 为代表的「自回归语言模型(即从左到右单向语言模型)+ Zero/Few Shot Prompt」模式。至于为何会分化成两条技术路线,有其必然性,关于这点我们放在后面解释。</p>\n\n<p>这两种模式,看似比较相像,但其背后蕴含了迥异的发展思路,也会导向不同的未来发展方向。不过遗憾的是,我们中的绝大多数人,在当时都低估了GPT 这条发展路线的潜力,而把视觉中心聚焦到了Bert这种模式上。</p>\n\n<h3 id=\"2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在 GPT 3.0 出现之后(20 年 6 月左右),一直到目前为止,我们应该正处于这个范式转换过程中。</p>\n\n<p>ChatGPT 是触发这次范型转换的关键节点,但是在 InstructGPT 出现之前,其实 LLM 处于这次范式转换前的一个过渡期。</p>\n\n<h4 id=\"21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</h4>\n\n<p>前面说过,在预训练模型发展的早期,技术框架收敛到了 Bert 模式和 GPT 模式这两种不同的技术范型,而且人们普遍更看好 Bert 模式一些,相当多数的后续技术改进,都是沿着 Bert 那条路走的。但是,随着技术的继续发展,你会发现,目前规模最大的 LLM 模型,几乎清一色都是类似 GPT 3.0 这种「自回归语言模型 + Prompting」模式的,比如 GPT-3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA 等,没有例外。为什么会这样呢?背后一定有其必然性,我认为可能主要源于两个原因。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-2.jpeg\" alt=\"image\" /></p>\n\n<p>首先,Google 的 T5 模型,在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示,标为红色的是个文本分类问题,黄色的是判断句子相似性的回归或分类问题,这都是典型的自然语言理解问题。在 T5 模型里,这些自然语言理解问题在输入输出形式上和生成问题保持了一致,也就是说,可以把分类问题转换成让 LLM 模型生成对应类别的字符串,这样理解和生成任务在表现形式就实现了完全的统一。</p>\n\n<p>这说明自然语言生成任务,在表现形式上可以兼容自然语言理解任务,若反过来,则很难做到这一点。这样的好处是:同一个 LLM 生成模型,可以解决几乎所有 NLP 问题。而如果仍然采取 Bert 模式,则这个 LLM 模型无法很好处理生成任务。既然这样,我们当然倾向于使用生成模型,这是一个原因。</p>\n\n<p>第二个原因,如果想要以零示例提示语(zero shot prompting)或少数示例提示语(few shot prompting)的方式做好任务,则必须要采取 GPT 模式。现在已有研究(参考:<a href=\"https://arxiv.org/pdf/2205.11726\">《On the Role of Bidirectionality in Language Model Pre-Training》</a>)证明:如果是以 fine-tuning 方式解决下游任务,Bert模式的效果优于 GPT 模式;若是以 zero shot / few shot prompting 这种模式解决下游任务,则GPT模式效果要优于 Bert 模式。这说明了,生成模型更容易做好 zero shot/few shot prompting 方式的任务,而Bert模式以这种方式做任务,是天然有劣势的。这是第二个原因。</p>\n\n<p>但是问题来了:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?要解释清楚这个问题,我们首先需要搞清楚另外一个问题:什么样的 LLM 模型,对我们是最理想的?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-3.jpeg\" alt=\"image\" /></p>\n\n<p>上图展示了一个理想的 LLM 该有的样子。首先,LLM 应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它,它应该能够自动从中学习到里面包含的所有知识点,学习过程不需要人的介入,并且能灵活应用所学知识,来解决实际问题。因为数据是海量的,要吸收所有知识,就要非常多的模型参数来存储知识,所以这个模型必然会是一个巨无霸模型。</p>\n\n<p>其次,LLM 应该能解决 NLP 任何子领域的问题,而不仅支持有限领域,甚至它应该可以响应 NLP 之外其它领域的问题,最好是任意领域的问题都能得到很好地回答。</p>\n\n<p>再者,当我们使用 LLM 解决某个具体领域问题的时候,应该用我们人类习惯的表达方式,就是说LLM应该理解人类的命令。这体现出让 LLM 适配人,而不是反过来,让人去适配 LLM 模型。人适配 LLM 的典型例子,比如绞尽脑汁去尝试各种不同的 prompt,以试图找到好的提示语,才能很好地解决手头问题。关于这点,上图在人类和 LLM 交互的接口层,举了几个例子,说明什么是好的人使用 LLM 模型的接口形式。</p>\n\n<p>看完这个理想中的 LLM,我们再回头解释上面遗留的问题:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?有两个原因。</p>\n\n<p>第一,这个 LLM 模型规模必然非常巨大,有能力作出这个模型,或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人,就算你把模型开源出来,他们也无力部署这个模型,更不用说再用 Fine-tuning 这种模式去修改模型参数了。所以,我们应该追求不修正模型参数,就能让任务需求方完成任务的方式,也就是应该采取 prompt 模式完成任务,而非 Fine-tuning 模式(由此可看出,soft prompting 技术方向是违背这个发展趋势的)。模型制作方则将 LLM 作成公用服务,以 LLM as Service 的模式运行。作为服务支持方,考虑到千变万化的用户需求,所以 LLM 模型制作方更要追求让 LLM 能完成尽可能多类型的任务,这是附带的影响,也是为何超级大模型一定会追求走向AGI的现实因素。</p>\n\n<p>第二,zero shot prompting 也好,few shot prompting 也好,甚至促进LLM推理能力的思维链(CoT,Chain of Thought)Prompting 也好,就是上图中接口层中的现有技术。具体而言,zero shot prompting 的初衷,其实就是人类和 LLM 的理想接口,直接用人类所习惯的任务表述方式让 LLM 做事情,但是发现 LLM 并不能很好地理解,效果也不好。经过继续研究,转而发现:对于某项任务,如果给 LLM 几个示例,用这些示例来代表任务描述,效果会比 zero shot prompting 好,于是大家都去研究更好的 few shot prompting 技术。可以理解为,本来我们希望 LLM 能够用人类常用的命令方式来执行某个任务,但是目前技术还做不到,所以退而求其次,用这些替代技术来表达人类的任务需求。</p>\n\n<p>如果理解了上述逻辑,很容易得出如下结论:few shot prompting(也被称为In Context Learning)只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务,而且 LLM 可以理解,那么,我们肯定会毫不犹豫地抛弃这些过渡期的技术,原因很明显,用这些方法来描述任务需求,并不符合人类的使用习惯。</p>\n\n<p>这也是为何我将 GPT 3.0 + Prompting 列为过渡期技术的原因,ChatGPT 的出现,改变了这个现状,用 Instruct 取代了 Prompting,由此带来新的技术范式转换,并产生若干后续影响。</p>\n\n<h3 id=\"影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</h3>\n\n<p>在理想 LLM 的背景下,我们再来看 ChatGPT,能更好理解它的技术贡献。ChatGPT 应该是目前所有的现有技术里,最接近理想 LLM 的技术方法。如果归纳下 ChatGPT 最突出特点的话,我会用下面八个字「能力强大,善解人意」。</p>\n\n<p>「能力强大」这一点,我相信应该主要归功于 ChatGPT 所依托的基础 LLM GPT-3.5。因为 ChatGPT 尽管加入了人工标注数据,但是量级只有数万,这个规模的数据量,和训练 GPT 3.5 模型使用的几千亿 token 级别的数据量相比,包含的世界知识(数据中包含的事实与常识)可谓沧海一粟,几可忽略,基本不会对增强 GPT 3.5 的基础能力发挥什么作用。所以它的强大功能,应该主要来自于隐藏在背后的 GPT 3.5。GPT 3.5 对标理想 LLM 模型中的那个巨无霸模型。</p>\n\n<p>那么,ChatGPT 向 GPT 3.5 模型注入新知识了吗?应该是注入了,这些知识就包含在几万人工标注数据里,不过注入的不是世界知识,而是人类偏好知识。所谓「人类偏好」,包含几方面的含义:首先,是人类表达一个任务的习惯说法。比如,人习惯说「把下面句子从中文翻译成英文」,以此表达一个「机器翻译」的需求,但是 LLM 又不是人,它怎么会理解这句话到底是什么意思呢?你得想办法让 LLM 理解这句命令的含义,并正确执行。所以,ChatGPT 通过人工标注数据,向GPT 3.5 注入了这类知识,方便 LLM 理解人的命令,这是它“善解人意”的关键。其次,对于什么是好的回答,什么是不好的回答,人类有自己的标准,例如比较详细的回答是好的,带有歧视内容的回答是不好的,诸如此类。这是人类自身对回答质量好坏的偏好。人通过 Reward Model 反馈给 LLM 的数据里,包含这类信息。总体而言,ChatGPT 把人类偏好知识注入 GPT 3.5,以此来获得一个听得懂人话、也比较礼貌的 LLM。</p>\n\n<p>可以看出,ChatGPT 的最大贡献在于:基本实现了理想 LLM 的接口层,让 LLM 适配人的习惯命令表达方式,而不是反过来让人去适配 LLM,绞尽脑汁地想出一个能 Work 的命令(这就是 instruct 技术出来之前,prompt 技术在做的事情),而这增加了 LLM 的易用性和用户体验。是 InstructGPT / ChatGPT 首先意识到这个问题,并给出了很好的解决方案,这也是它最大的技术贡献。相对之前的 few shot prompting,它是一种更符合人类表达习惯的人和 LLM 进行交互的人机接口技术。</p>\n\n<p>而这必将启发后续的 LLM 模型,继续在易用人机接口方面做进一步的工作,让 LLM 更听话。</p>\n\n<h3 id=\"影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</h3>\n\n<p>就 NLP 领域而言,这次范式转换,意味着很多目前独立存在的 NLP 研究领域,将被纳入 LLM 的技术体系,进而不再独立存在,逐步消失。经过第一次范式转换,尽管 NLP 中很多「中间任务」,继续作为独立研究领域存在不再必要,但是大多数「最终任务」,仍然是以独立研究领域存在的,只是切换成在「预训练 + fine-tuning」框架下,面对领域独有问题,陆续提出新的改进方案。</p>\n\n<p>目前研究表明,很多 NLP 任务,随着 LLM 模型规模增长,效果会大幅提升。据此,我觉得可得到如下推论:大多数某领域所谓「独有」的问题,大概率只是缺乏领域知识导致的一种外在表象,只要领域知识足够多,这个所谓领域独有的问题,就可以被很好地解决掉,其实并不需要专门针对某个具体领域问题,冥思苦想去提出专用解决方案。也许 AGI 的真相超乎意料地简单:你只要把这个领域更多的数据交给 LLM,让它自己学习更多知识即可。</p>\n\n<p>在这个背景下,同时,ChatGPT 证明了我们现在是可以直接去追求理想 LLM 模型的,那么,未来的技术发展趋势应该是:追求规模越来越大的 LLM 模型,通过增加预训练数据的多样性,来涵盖越来越多的领域,LLM 自主从领域数据中通过预训练过程学习领域知识,随着模型规模不断增大,很多问题随之得到解决。研究重心会投入到如何构建这个理想 LLM 模型,而非去解决某个领域的具体问题。这样,越来越多 NLP 的子领域会被纳入 LLM 的技术体系,进而逐步消失。</p>\n\n<p>我认为,判断某个具体领域是否该立即停止独立研究,其判断标准可采取以下两种方法,占其一即可:第一,判断某个任务,是否 LLM 的研究效果超过人类表现,对于那些 LLM 效果超过人类的研究领域,已无独立研究的必要。举个例子,GLUE 与 SuperGLUE 测试集合里的很多任务,目前 LLM 效果已超过人类表现,与这个数据集合密切相关的研究领域,其实就没有继续独立存在的必要。第二,对比两种模式的任务效果,第一种模式是用较大的领域专用数据进行 Fine-tuning,第二种是 few-shot prompting 或 instruct-based 方法。如果第二种方法效果达到或超过第一种方法,则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看,其实很多研究领域,目前 fine-tuning 效果还是占优的(因为这种模式领域训练数据量大),看似还可独立存在。但是考虑到很多任务随着模型规模增大,few shot prompting 效果持续增长,随着更大模型的出现,这个拐点很可能短期就会达到。</p>\n\n<p>如果上述猜测成立,将意味着如下残酷事实:对于很多 NLP 领域的研究人员,将面临往何处去的选择,是继续做领域独有问题呢?还是放弃这种看似前途不大的方式,转而去建设更好的LLM?如果选择转向去建设 LLM,又有哪些机构有能力、有条件去做这个事情呢?你对这个问题的回答会是什么呢?</p>\n\n<h3 id=\"影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</h3>\n\n<p>如果站在 AGI 的视角,参照之前描述的理想 LLM 模型,它所能完成的任务,不应局限于 NLP 领域,或某一两个学科领域,理想中的 LLM 应该是领域无关的通用人工智能模型,它现在在某一两个领域做得好,不代表只能做这些任务。ChatGPT 的出现,证明了现在这个时期,我们去追求AGI是有可行性的,而现在是抛开「领域学科」这个思维束缚的时候了。</p>\n\n<p>ChatGPT 除了展示出以流畅的对话形式解决各种 NLP 任务外,也具备强大的代码能力。很自然的,之后越来越多其它的研究领域,也会被逐步纳入 LLM 体系中,成为通用人工智能的一部分。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-4.jpeg\" alt=\"image\" /></p>\n\n<p>LLM 从 NLP 向外进行领域拓展,一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入,让LLM成为一个支持多模态输入输出的通用人机接口,典型的例子包括 DeepMind 的 Flamingo 和微软的<a href=\"https://arxiv.org/pdf/2206.06336.pdf\">《Language Models are General-Purpose Interfaces》</a>,上图展示了这种方式的概念结构。</p>\n\n<p>我的判断是无论是图像还是多模态,未来被融入 LLM 成为好用的功能,可能比我们想象的进度要慢。主要原因在于:尽管图像领域最近两年也一直在模仿 Bert 预训练的路子,尝试引入自监督学习,释放模型自主从图像数据中学习知识的能力,典型技术就是“对比学习”和 MAE,这是两条不同的技术路线。然而,从目前效果来看,尽管取得了很大的技术进步,但貌似这条路尚未走通,这体现在图像领域预训练模型应用到下游任务,带来的效果收益,远不如 Bert 或 GPT 应用在 NLP 下游任务那样显著。所以,图像预处理模型仍需深入探索,以释放图像数据的潜力,而这会迟滞它们被统一到 LLM 大模型的时间。当然,如果哪天这条路被趟通,大概率会复现NLP领域目前的局面,就是图像处理各个研究子领域可能会逐步消失,被融入到大型 LLM 中来,直接完成终端任务。</p>\n\n<p>除了图像与多模态,很明显,其它领域也会逐渐被纳入到理想 LLM 中来,这个方向方兴未艾,是具备高价值的研究主题。</p>\n\n<p>以上是我对范式转换的个人思考,接下来,我们来梳理下 GPT 3.0 之后 LLM 模型的主流技术进展。如理想 LLM 模型所示,相关的技术其实可以分为两大类;一类是关于 LLM 模型如何从数据中吸收知识,也包括模型规模增长对 LLM 吸收知识能力带来的影响;第二类是关于人如何使用 LLM 内在能力来解决任务的人机接口,包括In Context Learning 和 Instruct 两种模式。思维链(CoT)prompting 这种 LLM 推理技术,本质上也属于 In Context Learning,因为比较重要,我就把它们单独拎出来讲一下。</p>\n\n<h2 id=\"二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</h2>\n\n<p>从目前研究结果看,Transformer 是足够强大的特征抽取器,尚不需要做特别的改进。那么通过预训练过程,Transformer 学到了什么?知识是如何存取的?我们又如何修正错误知识?本节讲述这方面的研究进展。</p>\n\n<h3 id=\"1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</h3>\n\n<p>LLM 从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,可以分为语言类知识和世界知识两大类。</p>\n\n<p>语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于 LLM 能否捕获语言知识有较长研究历史,自从 Bert 出现以来就不断有相关研究,很早就有结论,各种实验充分证明 LLM 可以学习各种层次类型的语言学知识,这也是为何使用预训练模型后,各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外,各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在 Transformer 的低层和中层,而抽象的语言知识比如语义类知识,广泛分布在 Transformer 的中层和高层结构中。</p>\n\n<p>世界知识指的是在这个世界上发生的一些真实事件(事实型知识,Factual Knowledge),以及一些常识性知识(Common Sense Knowledge)。比如「拜登是现任美国总统」、「拜登是美国人」、「乌克兰总统泽连斯基与美国总统拜登举行会晤」,这些都是和拜登相关的事实类知识;而「人有两只眼睛」、「太阳从东方升起」这些属于常识性知识。关于 LLM 模型能否学习世界知识的研究也有很多,结论也比较一致:LLM 确实从训练数据中吸收了大量世界知识,而这类知识主要分布在 Transformer 的中层和高层,尤其聚集在中层。而且,随着 Transformer 模型层深增加,能够学习到的知识数量逐渐以指数级增加(可参考<a href=\"https://arxiv.org/pdf/2106.02902.pdf\">《BERTnesia: Investigating the capture and forgetting of knowledge in BERT》</a>)。其实,你把 LLM 看作是一种以模型参数体现的隐式知识图谱,如果这么理解,我认为是一点问题也没有的。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2011.04946\">《When Do You Need Billions of Words of Pre-training Data?》</a>这篇文章研究了预训练模型学习到的知识量与训练数据量的关系,它的结论是:对于 Bert 类型的语言模型来说,只用 1000 万到 1 亿单词的语料,就能学好句法语义等语言学知识,但是要学习事实类知识,则要更多的训练数据。这个结论其实也是在意料中的,毕竟语言学知识相对有限且静态,而事实类知识则数量巨大,且处于不断变化过程中。而目前研究证明了随着增加训练数据量,预训练模型在各种下游任务中效果越好,这说明了从增量的训练数据中学到的更主要是世界知识。</p>\n\n<h3 id=\"2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</h3>\n\n<p>由上可知,LLM 确实从数据中学到了很多语言类及世界知识。那么,对于某条具体的知识,LLM 把它存储到了哪里?又是如何提取出来的?这也是一个有意思的问题。</p>\n\n<p>显然,知识一定存储在 Transformer 的模型参数里。从 Transformer 的结构看,模型参数由两部分构成:<strong>多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中</strong>。MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点,那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-5.jpeg\" alt=\"image\" /></p>\n\n<p>但这样的定位,粒度还是太粗,无法很好回答具体某条知识是如何存储与提取的,比如「中国的首都是北京」这条知识,以三元组表达就是<北京,is-capital-of,中国>,其中「is-capital-of」代表实体间关系。<strong>这条知识它存储在 LLM 的哪里呢?</strong></p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:这些知识存哪了?我们现在有以下几点认知:<br />\n1、多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中。<br />\n2、MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点。<br />\n3、那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。<br />\n4、一些研究达成共识:Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,比如《Transformer Feed-Forward Layers Are Key-Value Memories》</p>\n</blockquote>\n\n<p><a href=\"https://arxiv.org/pdf/2012.14913.pdf\">《Transformer Feed-Forward Layers Are Key-Value Memories》</a>给出了一个比较新颖的观察视角,它把 Transformer 的 FFN 看成存储大量具体知识的 Key-Value 存储器。如上图所示(图左是原始论文图,其实不太好理解,可以看做了注释的图右,更好理解些),FFN 的第一层是个 MLP 宽隐层,这是 Key 层;第二层是 MLP 窄隐层,是 Value 层。FFN 的输入层其实是某个单词对应的 MHA 的输出结果Embedding,也就是通过 Self Attention,将整个句子有关的输入上下文集成到一起的 Embedding,代表了整个输入句子的整体信息。</p>\n\n<p>Key 层的每个神经元节点,记载了一对信息。比如对于上图中 FFN 第一个隐层的第 i 个节点 ki,也许就是它记载了 <北京,is-capital-of,中国> 这条知识。ki 节点对应的 key 向量,其实指的是节点 ki 和输入层每个节点的权重向量;而对应的 Value 向量,指的是节点 ki 和 FFN 第二层的 Value 层每个节点形成连接的权重向量。每个神经元的 Key 向量,用于识别输入中的某种语言或者知识模式,是一种模式探测器。如果输入中包含它要检测的某种模式,那么输入向量和 ki 节点的 key 权重进行向量内积计算,加上 Relu,形成 ki 的大数值响应,意味着 ki 检测到了这个模式,于是再把这个响应值,通过 ki 节点的 Value 权重向量向 FFN 第二层传播。这等价于将 Value 向量的值,用响应值加权,然后传递并体现到第二层 Value 层每个节点的输出上。如此这般,FFN 的正向传播计算过程,看起来就像是通过 Key 检测到某种知识模式,然后取出对应的 Value,并把 Value 体现在FFN的第二层输出上。当然,FFN 第二层每个节点,会收集 FFN 的 Key 层所有节点信息,所以是一种混合响应,而 Value 层所有节点的混合响应,可以解读为代表输出单词的概率分布信息。</p>\n\n<p>听着可能还是比较复杂,我们用个极端的例子来说明。我们假设上图的节点 ki就是记载 <北京,is-capital-of,中国>这条知识的 Key-Value 存储器,它的 Key 向量,用于检测「中国的首都是…」这个知识模式,它的 Value 向量,基本存储了与单词「北京」的 Embedding 比较接近的向量。当 Transformer 的输入是「中国的首都是[Mask]」的时候,ki 节点从输入层探测到这个知识模式,所以产生较大的响应输出。我们假设 Key 层其它神经元对这个输入都没有任何响应,那么对应的Value层的节点,其实只会接收到「北京」这个 Value 对应的单词 embedding,并通过 ki的大响应值,进行了进一步的数值放大。于是,Mask 位置对应的输出,就自然会输出「北京」这个单词。基本就是这么个过程,看着很复杂,其实很简单。</p>\n\n<p>而且这篇文章还指出,Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,就是说低层FFN存储词法、句法等表层知识,中层和高层存储语义及事实概念知识,这和其它研究结论是一致的。</p>\n\n<p><strong>要我猜,把 FFN 看成 Key-Value 存储器这种思路,很可能不是最终的正确答案,但是距离最终正确答案的距离,估计也不太远</strong>。</p>\n\n<h3 id=\"3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</h3>\n\n<p>既然我们已知具体的某条世界知识存储在某个或者某些 FFN 节点的参数里,自然会引发另外一个问题:我们能否修正 LLM 模型里存储的错误或者过时的知识呢?比如对于问题「英国的现任首相是谁?」鉴于近年来英国首相频繁更迭,你猜 LLM 更倾向输出「鲍里斯」还是更青睐「苏纳克」?很明显训练数据中包含“鲍里斯”的数据会更多,这种情况很大可能 LLM 会给出错误回答,于是我们就有修正 LLM 里存储的过时知识的必要性。</p>\n\n<p>如果归纳下,目前有三类不同方法来修正 LLM 里蕴含的知识:</p>\n\n<p>第一类方法从训练数据的源头来修正知识。<a href=\"https://arxiv.org/pdf/2205.11482.pdf\">《Towards Tracing Factual Knowledge in Language Models Back to the Training Data》</a>这篇文章的研究目标是:对于指定的某条知识,我们是否可以定位到是哪些训练数据导致 LLM 学会了这条知识?答案是肯定的,这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术,假设我们想要删除某条知识,则可首先定位到其对应的数据源头,删除数据源,然后重新预训练整个 LLM 模型,这样即可达成删除 LLM 中相关知识的目的。但是这里有个问题,如果修正一小部分知识,我们就需要重新做一次模型预训练,这样做明显成本太高。所以这种方法不会太有发展前景,可能比较适合那种对于某个特定类别数据的一次性大规模删除场合,不适合少量多次的常规知识修正场景,比如可能比较适合用来做去除偏见等去 toxic 内容的处理。</p>\n\n<p>第二类方法是对 LLM 模型做一次 fine-tuning 来修正知识。一个直观能想到的方法是:我们可以根据要修正成的新知识来构建训练数据,然后让 LLM 模型在这个训练数据上做 fine-tuning,这样指导 LLM 记住新的知识,遗忘旧的知识。这个方法简单直观,但是也有一些问题,首先它会带来灾难遗忘问题,就是说除了忘掉该忘的知识,还忘掉了不该忘的知识,导致这么做了之后有些下游任务效果下降。另外,因为目前的 LLM 模型规模非常大,即使是做 fine-tuning,如果次数频繁,其实成本也相当高。对这种方法感兴趣的可以参考<a href=\"https://arxiv.org/pdf/2012.00363.pdf\">《Modifying Memories in Transformer Models》</a>。</p>\n\n<p>另外一类方法直接修改 LLM 里某些知识对应的模型参数来修正知识。假设我们想要把旧知识 <英国,现任首相,鲍里斯>,修正到 <英国,现任首相,苏纳克>。首先我们想办法在 LLM 模型参数中,定位到存储旧知识的 FFN 节点,然后可以强行调整更改 FFN 中对应的模型参数,将旧知识替换成新的知识。可以看出,这种方法涉及到两项关键技术:首先是如何在 LLM 参数空间中定位某条知识的具体存储位置;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。理解这个修正 LLM 知识的过程,其实对于更深入理解 LLM 的内部运作机制是很有帮助的。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:如何修改已存储的知识?<br />\n首先是如何定位存哪了;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。</p>\n</blockquote>\n\n<h2 id=\"三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</h2>\n\n<p>我们知道,近年来,LLM 模型规模在快速增长,目前效果最好的 LLM 模型,其参数规模大都超过了千亿(100B)参数规模。比如,OpenAI 的 GPT 3 的规模为 175B,Google 的 LaMDA 规模为 137B,PaLM 的规模为 540B,DeepMind 的 Gogher 规模为 280B 等,不一而足。国内也有中文巨型模型,比如智源 GLM 规模 130B,华为「盘古」规模 200B,百度「文心」规模 260B,浪潮「源1.0」规模 245B。那么,一个很自然的问题就是:随着 LLM 模型规模不断增长,会发生些什么呢?</p>\n\n<p>预训练模型的应用往往是两阶段的:预训练阶段,及具体场景应用阶段。在预训练阶段,其优化目标是交叉熵,对 GPT 这种自回归语言模型来说,也就是看 LLM 是否正确预测到了下一个单词;而场景应用阶段,一般要看具体场景的评价指标。一般我们的直觉是:如果 LLM 模型在预训练阶段的指标越好,自然它解决下游任务的能力就越强。然而,事实并非完全如此。现有研究已证明,预训练阶段的优化指标确实和下游任务表现出正相关关系,但是并非完全正相关。也就是说,只看预训练阶段的指标,来判断一个 LLM 模型是否够好,这是不够的。基于此,我们分头来看在这两个不同阶段,随着 LLM 模型增大,有什么影响。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-6.jpeg\" alt=\"image\" /></p>\n\n<p>首先,我们先看在预训练阶段,随着模型规模逐步增大,会发生什么。OpenAI 在<a href=\"https://arxiv.org/pdf/2001.08361\">《Scaling Laws for Neural Language Models》</a>中专门研究了这个问题,并提出 LLM 模型所遵循的「伸缩法则(scaling law)」。如上图所示,这个研究证明:<strong>当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好</strong>。</p>\n\n<p>既然三个因素都重要,那么我们在实际做预训练的时候,就有一个算力如何分配的决策问题:假设用于训练 LLM 的算力总预算(比如多少 GPU 小时或者 GPU 天)给定,那么是应该多增加数据量、减少模型参数呢?还是说数据量和模型规模同时增加,减少训练步数呢?此消彼长,某个要素规模增长,就要降低其它因素的规模,以维持总算力不变,所以这里有各种可能的算力分配方案。最终 OpenAI 选择了同时增加训练数据量和模型参数,但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了:对于训练数据量和模型参数这两个要素,如果只单独增加其中某一个,这不是最好的选择,最好能按照一定比例同时增加两者,它的结论是优先增加模型参数,然后才是训练数据量。假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 5.5 倍的模型参数量,1.8 倍的训练数据量,此时模型效果最佳。</p>\n\n<p>DeepMind 的一项研究(参考<a href=\"https://arxiv.org/pdf/2203.15556\">《Training Compute-Optimal Large Language Models》</a>)更深入地探究了这个问题,其基本结论和 OpenAI 的结论差不多,比如确实需要同时增加训练数据量和模型参数,模型效果才会更好。而很多大模型在做预训练的时候,并没有考虑这一点,很多 LLM 大模型只是单调增加模型参数,而固定住了训练数据量,这个做法其实是不对的,限制了 LLM 模型的潜力。但是它修正了两者的比例关系,<strong>认为训练数据量和模型参数是同等重要的,也就是说,假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 3.3 倍的模型参数量,3.3 倍的训练数据量,这样模型效果才最好</strong>。</p>\n\n<p>这意味着:增加训练数据量的重要性,比我们之前所认为的,还要重要。基于这个认知,DeepMind 在设计 Chinchilla 模型时,在算力分配上选择了另外一种配置:对标数据量 300B、模型参数量 280B 的 Gopher 模型,Chinchilla 选择增加 4 倍的训练数据,但是将模型参数降低为 Gopher 的四分之一,大约为70B。但是无论预训练指标,还是很多下游任务指标,Chinchilla 效果都要优于规模更大的 Gopher。</p>\n\n<p>这带给我们如下启示:我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。缩小模型规模有很多好处,比如在应用的时候,推理速度会快很多等,无疑这是一个很有前途的 LLM 发展路线。</p>\n\n<p>以上是从预训练阶段来看模型规模的影响,如果从 LLM 解决下游具体任务效果的角度来看,随着模型规模增大,不同类型的任务有不同的表现,具体而言,有以下三类情况。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-7.jpeg\" alt=\"image\" /></p>\n\n<p>第一类任务完美体现了 LLM 模型的 scaling law,就是说随着模型规模逐步放大,任务的表现越来越好,如上图里的(a)图所示。这类任务通常符合如下共性:它们往往都是知识密集型任务,也就是说如果 LLM 模型包含的知识量越多,这类任务表现越好。而很多研究已经证明越大的 LLM 模型学习效率越高,也就是说相同训练数据量,模型越大任务效果越好,说明面对的即使是同样的一批训练数据,更大的 LLM 模型相对规模小一些的模型,从中学到了更多的知识。更何况一般情况下,在增大 LLM 模型参数的时候,往往会同步增加训练数据量,这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图,为何随着模型规模增大,这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务,其实都属于这种知识密集型任务,而很多任务在近两年获得了极大的效果提升,甚至超过了人类表现。很明显,这大概率是 LLM 模型的规模增长带来的,而非归功于某项具体的技术改进。</p>\n\n<p>第二类任务展现出 LLM 具备某种「<strong>涌现能力(Emergent Ability)</strong>」,如上图(b)所示。所谓「涌现能力」,指的是当模型参数规模未能达到某个阀值时,模型基本不具备解决此类任务的任何能力,体现为其性能和随机选择答案效果相当,但是当模型规模跨过阀值,LLM 模型对此类任务的效果就出现突然的性能增长。也就是说,模型规模是解锁(unlock)LLM 新能力的关键,随着模型规模越来越大,会逐渐解锁 LLM 越来越多的新能力。这是个很神奇的现象,因为它意味着如下让人对未来可报乐观预期的可能:或许很多任务,目前 LLM 还不能很好地解决,甚至站在现在这个时刻的我们看起来,LLM 完全没有能力解决这类任务,但因 LLM 具备「涌现能力」,所以如果我们继续推大模型,也许某一天它的这项能力就被突然解锁了。LLM 模型的规模增长会给我们带来意想不到的精彩礼物。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2206.04615\">《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》</a>这篇文章指出,这类<strong>体现出「涌现能力」的任务也有一些共性:这些任务一般由多步骤构成,要解决这些任务,往往需要先解决多个中间步骤,而逻辑推理能力在最终解决这类任务中发挥重要作用</strong>。思维链(Chain of Thought)Prompting 是典型的增强 LLM 推理能力的技术,能大幅提升此类任务的效果,关于 CoT 技术,在随后小节内容会做解释,此处暂不展开。</p>\n\n<p>问题是,为何 LLM 会出现这种「涌现能力」现象呢?上述文章以及<a href=\"https://arxiv.org/pdf/2206.07682\">《Emergent Abilities of Large Language Models》</a>给出了几个可能的解释:</p>\n\n<p>一种可能解释是<strong>有些任务的评价指标不够平滑</strong>。比如说有些生成任务的判断标准,它要求模型输出的字符串,要和标准答案完全匹配才算对,否则就是 0 分。所以,即使随着模型增大,其效果在逐步变好,体现为输出了更多的正确字符片段,但是因为没有完全对,只要有任何小错误都给 0 分,只有当模型足够大,输出片段全部正确才能得分。也就是说,因为指标不够平滑,所以不能体现 LLM 其实正在逐步改善任务效果这一现实,看起来就是「涌现能力」这种外在表现。</p>\n\n<p>另外一种可能的解释是:有些任务由若干中间步骤构成,随着模型规模增大,解决每个步骤的能力也在逐步增强,但是只要有一个中间步骤是错的,最终答案就是错的,于是也会导致这种表面的「涌现能力」现象。</p>\n\n<p>当然,<strong>上面的解释目前还都是猜想,至于为何 LLM 会出现这种现象,还需要进一步更深入的研究</strong>。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-8.jpeg\" alt=\"image\" /></p>\n\n<p>还有少部分任务,随着模型规模增长,任务的效果曲线展现出 U 形特性:随着模型规模逐渐变大,任务效果逐渐变差,但是当模型规模进一步增长,则效果开始越来越好,呈现出 U 形增长趋势,如上图所示的粉红色 PaLM 模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢?<a href=\"https://arxiv.org/pdf/2211.02011\">《Inverse scaling can become U-shaped》</a>这篇文章给出了一种解释:这些任务,内部其实隐含了两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。当模型规模小的时候,无法识别任意一种子任务,所以模型的表现跟随机选择答案差不多,当模型增长到中等规模的时候,主要执行的是干扰任务,所以对真正的任务效果有负面影响,体现为真正任务效果的下降,而当进一步增加模型规模,则 LLM 可以忽略干扰任务,执行真正的任务,体现为效果开始增长。</p>\n\n<p>对于那些随着模型规模增大,效果一直下降的任务,如果采用思维链(CoT)Prompting,则部分任务的表现转换为遵循 Scaling law,即模型规模越大效果越好,而其它任务则转换为U性增长曲线。这其实侧面说明了:此类任务应属于推理类型的任务,所以加入 CoT 后任务表现会发生质的变化。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:训练数据规模、模型参数规模和训练时长(步数),与最终 LLM 性能(loss 衡量)之间什么关系?<br />\n1、当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好。<br />\n2、训练数据量和模型参数是同等重要的。<br />\n3、我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么有些模型不遵循 scaling law?三类任务:<br />\n第一类完美遵循 scaling low。<br />\n第二类过了阈值后涌现。《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》和《Emergent Abilities of Large Language Models》认为是指标不平滑 or 中间步骤是错的。\n第三类 U 型,《Inverse scaling can become U-shaped》猜测可能两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。</p>\n</blockquote>\n\n<h2 id=\"四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</h2>\n\n<p>一般我们经常提到的人和 LLM 的接口技术包括:zero shot prompting、few shot prompting、In Context Learning,以及 Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献,会发现叫法比较乱。</p>\n\n<p>其中 Instruct 是 ChatGPT 的接口方式,就是说人以自然语言给出任务的描述,比如「把这个句子从中文翻译成英文」,类似这种。zero shot prompting 我理解其实就是现在的 Instruct 的早期叫法,以前大家习惯叫 zero shot,现在很多改成叫 Instruct。尽管是一个内涵,但是具体做法是两种做法。早期大家做 zero shot prompting,实际上就是不知道怎么表达一个任务才好,于是就换不同的单词或者句子,反复在尝试好的任务表达方式,这种做法目前已经被证明是在拟合训练数据的分布,其实没啥意思。目前 Instruct 的做法则是给定命令表述语句,试图让 LLM 理解它。所以尽管表面都是任务的表述,但是思路是不同的。</p>\n\n<p>而In Context Learning 和 few shot prompting 意思类似,就是给 LLM 几个示例作为范本,然后让LLM解决新问题。我个人认为 In Context Learning 也可以理解为某项任务的描述,只是 Instruct 是一种抽象的描述方式,In Context Learning 是一种例子示范的例子说明法。当然,鉴于目前这几个叫法用的有点乱,所以上述理解仅代表个人看法。</p>\n\n<p>所以我们此处只对 In Context Learning 和 Instruct 进行介绍,不再提 zero shot 和 few shot 了。</p>\n\n<h3 id=\"1神秘的-in-context-learning\">1、神秘的 In Context Learning</h3>\n\n<p>如果你细想,会发现 In Context Learning 是个很神奇的技术。它神奇在哪里呢?神奇在你提供给 LLM 几个样本示例,….,然后给它 x(n+1),LLM 竟然能够成功预测对应的 y(n+1)。听到这你会反问:这有什么神奇的呢?Fine-tuning 不就是这样工作的吗?你要这么问的话,说明你对这个问题想得还不够深入。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-9.jpeg\" alt=\"image\" /></p>\n\n<p>Fine-tuning 和 In Context Learning 表面看似都提供了一些例子给 LLM,但两者有质的不同(参考上图示意):Fine-tuning 拿这些例子当作训练数据,利用反向传播去修正 LLM 的模型参数,而修正模型参数这个动作,确实体现了 LLM 从这些例子学习的过程。但是,In Context Learning 只是拿出例子让 LLM 看了一眼,并没有根据例子,用反向传播去修正 LLM 模型参数的动作,就要求它去预测新例子。既然没有修正模型参数,这意味着貌似 LLM 并未经历一个学习过程,如果没有经历学习过程,那它为何能够做到仅看一眼,就能预测对新例子呢?这正是 In Context Learning 的神奇之处。这是否让你想起了一句歌词「只是因为在人群中多看了你一眼 再也没能忘掉你容颜」,而这首歌名叫「传奇」。你说传奇不传奇?</p>\n\n<p>看似 In Context Learning 没从例子里学习知识,实际上,难道 LLM 通过一种奇怪的方式去学习?还是说,它确实也没学啥?关于这个问题的答案,目前仍是未解之谜。现有一些研究各有各的说法,五花八门,很难判断哪个讲述的是事实的真相,甚至有些研究结论还相互矛盾。这里提供几个目前的说法,至于谁对谁错,只能你自己把握了。当然,我认为追求这个神奇现象背后的真相,是一个好的研究课题。</p>\n\n<p>试图证明 In Context Learning 没有从例子中学习的工作是<a href=\"https://arxiv.org/pdf/2202.12837\">《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》</a>。它发现了:在提供给 LLM 的样本示例中,yi 是否 xi 对应的正确答案,其实并不重要,如果我们把正确答案 yi 替换成随机的另外一个答案 yj ,这并不影响 In Context Learning 的效果。这起码说明了一点:In Context Learning 并没有提供给 LLM 那个从 x 映射到 y 的映射函数信息:y=f(x),否则的话你乱换正确标签,肯定会扰乱这个 y=f(x) 映射函数。也就是说,In Context Learning 并未学习这个输入空间到输出空间的映射过程。</p>\n\n<p>真正对 In Context Learning 影响比较大的是:x 和 y 的分布,也就是输入文本 x 的分布和候选答案 y 有哪些,如果你改变这两个分布,比如把 y 替换成候选答案之外的内容,则 In Context Learning 效果急剧下降。</p>\n\n<p>总之,这个工作证明了 In Context Learning 并未学习映射函数,但是输入和输出的分布很重要,这两个不能乱改。</p>\n\n<p>有些工作认为 LLM 还是从给出的示例学习了这个映射函数 y=f(x),不过是种隐式地学习。比如<a href=\"https://arxiv.org/pdf/2211.15661.pdf\">《What learning algorithm is in-context learning? Investigations with linear models》</a>认为 Transformer 能够隐式地从示例中学习 x 到 y 的映射过程,它的激活函数中包含了一些简单映射函数,而 LLM 通过示例能够激发对应的那一个。而<a href=\"https://arxiv.org/pdf/2212.10559\">《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》</a>这篇文章则将 ICL 看作是一种隐式的 Fine-tuning。</p>\n\n<p>总而言之,<strong>目前这还是一个未解之谜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】LLM 技术增量重点</strong>:In Context Learning</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:为什么 In Context Learning 有效?<br />\n1、《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》认为 ICL 没有从例子里学习 x 到 y 的映射关系,而只是学习了分布与分布的对应。<br />\n2、《What learning algorithm is in-context learning? Investigations with linear models》认为 ICL 隐式地学习了 x 到 y 的映射关系。<br />\n3、《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》认为 ICL 是隐式的 fine-tuning。</p>\n</blockquote>\n\n<h3 id=\"2神奇的-instruct-理解\">2、神奇的 Instruct 理解</h3>\n\n<p>我们可以把 Instruct 当作一种方便人类理解的任务表述,在这个前提下,目前关于 Instruct 的研究可以分成两种:偏学术研究的 Instruct,以及关于人类真实需求描述的 Instruct。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-10.jpeg\" alt=\"image\" /></p>\n\n<p>我们先来看第一种:偏学术研究的 Instruct。它的核心研究主题是多任务场景下,LLM 模型对 Instruct 理解的泛化能力。如上图中 FLAN 模型所示,就是说有很多 NLP 任务,对于每个任务,研究人员构造一个或者多个 Prompt 模版作为任务的 Instruct,然后用训练例子对 LLM 模型进行微调,让 LLM 以同时学习多个任务。训练好模型后,给 LLM 模型一个它没见过的全新任务的 Instruct,然后让 LLM 解决 zero shot 任务,从任务解决得是否足够好,来判断 LLM 模型是否有对 Instruct 理解的泛化能力。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】FLAN 是什么?</strong>2021 年 9 月 Google Research 团队在文章<a href=\"https://arxiv.org/pdf/2109.01652\">《Finetuned Language Models Are Zero-Shot Learners》</a>中提出的「Finetuned Language 」<br />\n1、FLAN 是 Finetuned LAnguage Net 的缩写,它用 Multi-task Learning 的方法和一种别出心裁的微调方式对 PLM 进行微调,在参数少 400 亿的情况下,性能超越 GPT-3。<br />\n2、部分参考自:https://juejin.cn/post/7064919723498012703 <br />\n3、FLAN 训练:对多个任务,把 Prompt 模板写成 Instruct,以此微调来学习。<br />\n4、FLAN 测试:新类型的任务,直接 zero-shot,判断 LLM 对 Instruct 理解的泛化能力。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】如何增加 LLM 模型 Instruct 泛化能力</strong> <br />\n1、增加训练任务的数量 <br />\n2、增加训练任务的类型多样性 <br />\n3、增加 LLM 模型参数规模 <br />\n4、提供 CoT Prompting</p>\n</blockquote>\n\n<p>如果归纳下目前的研究结论(可参考<a href=\"https://arxiv.org/pdf/2210.11416\">《Scaling Instruction-Finetuned Language Models》</a>/<a href=\"https://arxiv.org/pdf/2204.07705\">《Super-Natural Instructions: Generalization via Declarative Instructions on 1600+ NLP Tasks》</a>),能够有效增加 LLM 模型 Instruct 泛化能力的因素包括:增加多任务的任务数量、增加 LLM 模型大小、提供 CoT Prompting, 以及增加任务的多样性。如果采取任意一项措施,都可以增加 LLM 模型的 Instruct 理解能力。</p>\n\n<p>第二种是人类真实需求下的 Instruct,这类研究以 InstructGPT 和 ChatGPT 为代表。这类工作也是基于多任务的,但是和偏向学术研究类工作最大的不同,在于它是面向人类用户真实需求的。为什么这么说呢?因为它们用于 LLM 多任务训练的任务描述 Prompt,是从大量用户提交的真实请求中抽样而来的,而不是固定好研究任务的范围,然后让研究人员来写任务描述 prompt。这里所谓的「真实需求」,体现在两个方面:首先,因为是从用户提交的任务描述里随机抽取的,所以涵盖的任务类型更多样化,也更符合用户的真实需求;其次,某个任务的 prompt 描述,是用户提交的,体现了一般用户在表达任务需求时会怎么说,而不是你认为用户会怎么说。很明显,这类工作改出来的 LLM 模型,用户体验会更好。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2203.02155.pdf\">InstructGPT 论文</a>里,也拿这种方法和 FLAN 那种 Instruct based 方法做了比较。首先在 GPT3 上用 FLAN 提到的任务、数据以及 Prompt 模版进行微调,来在 GPT 3 上复现 FLAN 方法,然后和 InstructGPT 进行比较,因为 InstructGPT 的基础模型也是 GPT3,所以只有数据和方法的差别,两者可比,结果发现 FLAN 方法的效果,距离 InstructGPT 有很大的差距。那么背后的原因是什么呢?论文分析数据后认为,<strong>FLAN 方法涉及到的任务领域相对少</strong>,是 InstructGPT 涉及领域的子集,所以效果不好。也就是说,<strong>FLAN 论文里涉及到的任务和用户真实需求是不符的</strong>,而这导致在真实场景下效果不够好。而这对我们的启示是:从用户数据中收集真实需求,这事情是很重要的。</p>\n\n<blockquote>\n <p><strong>LLM 技术增量重点</strong>:Instruct <br />\n1、FLAN 的任务领域太少。<br />\n2、FLAN 不是从用户数据收集真实需求(研究人员构造任务),与用户真实需求不符。</p>\n</blockquote>\n\n<h3 id=\"3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</h3>\n\n<p>如果我们假设 In Context Learning 是用一些例子来具象地表达任务命令,Instruct 是一种更符合人类习惯的抽象任务描述。那么,一个很自然的问题是:它们之间有什么联系吗?比如,我们是否能够提供给 LLM 完成某个任务的若干具体示例,让 LLM 找出其对应的自然语言描述的 Instruct 命令?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-11.jpeg\" alt=\"image\" /></p>\n\n<p>目前有零星的工作在探索这个问题,我认为这个方向是很有研究价值的。先说答案,答案是:Yes,LLM Can。<a href=\"https://arxiv.org/pdf/2211.01910\">《Large Language Models Are Human-Level Prompt Engineers》</a>是做这个方向很有趣的工作,如上图所示,对于某项任务,给 LLM 一些示例,让 LLM 自动生成能够描述这项任务的自然语言命令,然后它再用 LLM 生成的任务描述去测试任务效果。它使用的基础模型是 GPT 3 和 InstructGPT,经过这项技术加持后,LLM 生成的 Instruct 的效果相比未采用这项技术的 GPT 3 以及 InstuctGPT 来说,指标有极大地提升,而且在一些任务上超过人类的表现。</p>\n\n<p>这说明了:<strong>具象的任务示例和任务的自然语言描述之间,有种神秘的内在联系。至于这种联系到底是什么?我们目前对此还一无所知</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:Instruct 和 ICL 之间的联系 <br />\n1、ICL 是给了一些具象例子的命令 <br />\n2、Instruct 相当于是抽象命令</p>\n</blockquote>\n\n<h2 id=\"五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</h2>\n\n<p>目前很多研究已证明 LLM 对于知识具有强大的记忆能力,但是,一般我们不会因为一个人记忆能力强,就说这人很聪明,是否具有强大的推理能力,往往是我们判断一个人是否聪明的重要标准。类似的,如果 LLM 的效果想让人觉得很惊艳,强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点,去推导出新知识或新结论。关于 LLM 的推理能力,是最近一年来 LLM 里最重要和热门的研究领域之一。于是,我们关心的问题就是:<strong>LLM 具备推理能力吗?如果具备,那么它的推理能力够强吗?</strong></p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:LLM 具备推理能力吗?</p>\n</blockquote>\n\n<p>这两个问题目前的答案似乎应该是:当模型规模足够大的时候,LLM 本身是具备推理能力的,在简单推理问题上,LLM 已经达到了很好的能力,但是复杂推理问题上,还需要更多深入的研究。</p>\n\n<p>如果梳理现有 LLM 推理相关工作的话,我把它们归到两大类,体现出挖掘或促进 LLM 推理能力不同的技术思路:第一类研究比较多,可以统称为<strong>基于 Prompt 的方法</strong>,核心思想是通过合适的提示语或提示样本,更好地激发出 LLM 本身就具备的推理能力,Google 在这个方向做了大量很有成效的工作。第二类做法是在<strong>预训练过程中引入程序代码</strong>,和文本一起参与预训练,以此进一步增强 LLM 的推理能力,这应该是 OpenAI 实践出的思路。比如 ChatGPT 肯定具备很强的推理能力,但它并不要求用户必须提供一些推理示例,所以 <strong>ChatGPT 强大的推理能力,大概率来源于使用代码参与 GPT 3.5 的预训练</strong>。</p>\n\n<p>这两种思路其实大方向是迥异的:利用代码增强 LLM 推理能力,这体现出一种通过增加多样性的训练数据,来直接增强 LLM 推理能力的思路;而基于 Prompt 的方法,它并不会促进 LLM 本身的推理能力,只是让 LLM 在解决问题过程中更好地展示出这种能力的技术方法。可以看出,前者(代码方法)治本,后者治标。当然,两者其实也是互补的,但从长远看,治本的方法更重要。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】挖掘或促进 LLM 推理能力的两个技术思路</strong>:<br />\n1、Google 有大量研究成果的基于 Prompt 的方法:对应 ICL,挖掘 LLM 的推理能力 —— 基于神奇的 ICL <br />\n2、OpenAI 实践出真知的策略 —— Pre-training 时引入程序代码</p>\n</blockquote>\n\n<h3 id=\"1基于-prompt-的方法\">1、基于 Prompt 的方法</h3>\n\n<p>这方面工作非常多,如果归纳一下的话,大致可以分为三条技术路线。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-12.jpeg\" alt=\"image\" /></p>\n\n<p>第一种思路是直接在问题上追加辅助推理 Prompt。这种方法简单直接,但在众多领域都很有效。这个做法是由<a href=\"https://arxiv.org/pdf/2205.11916\">《Large language models are zero-shot reasoners》</a>提出的,也被称为 zero-shot CoT。具体而言,分为两个阶段(如上图所示),第一阶段在提问的问题上追加「Let’s think step by step」这句提示语,LLM 会输出具体的推理过程;第二阶段,在第一阶段的问题后,拼接 LLM 输出的具体推理过程,并再追加 Prompt=“Therefore, the answer (arabic numerals) is”,此时 LLM 会给出答案。如此简单的操作,却可以大幅增加 LLM 在各项推理任务中的效果,比如在数学推理测试集 GSM8K 上,加上提示语后,推理准确率直接从原先的 10.4% 提升到了 40.4%,可谓神奇。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>加上「Let’s think step by step」立马就提升了 GSM8K 数学推理测试集的准确率 +30pt!</p>\n</blockquote>\n\n<p>为什么 LLM 会具备给一句「Let’s think step by step」提示语,就能列出详细的推理步骤并算出答案呢?其原因目前尚无定论,我的猜测是:<strong>很可能因为预训练数据里面存在大量的此种数据,就是以「Let’s think step by step」开头,然后后面是详细的推理步骤,最后给出答案,而 LLM 在预训练的时候记住了这些模式。而当我们输入这个提示语的时候,激发 LLM 模糊得「回忆」起某些例子的推导步骤,于是即可模仿这些例子进行步骤推理并给出答案</strong>。当然这只是我的无依据推论,若事实真的如此,如果你看过后面介绍的标准 CoT 做法,会发现 Zero-shot CoT 本质上和标准 CoT 很可能没什么区别,只是标准 CoT 由人工来写推理步骤的示例,而 Zero-shot CoT 大概率是通过提示语,激活了记忆中的某些包含推理步骤的示例,很可能是如此区别。而标准 CoT 效果比 Zero-Shot CoT 效果好也完全可以理解,因为毕竟靠 LLM 回忆示例,精准性估计不会太高,而人工给出的示例,准确性是有保障的,所以自然标准 CoT 效果会更好。</p>\n\n<p><strong>这侧面说明了一个道理,就是 LLM 本身是具备推理能力的</strong>,只是我们没有办法把它的这种能力激发出来而已,通过合适的提示语来进行两步提示,就在一定程度上可以释放出它的这种潜力。另外,对于中文,很可能存在另外一个黄金提示语,比如「详细解题思路如下」,类似这种,因为中文语料在讲解推理步骤的时候,经常用的引导句和「让我们一步一步来思考」应该是不同的,这是明显的西方说法,而探索出这个中文黄金提示语,其实也是很有必要的。</p>\n\n<p>第二种思路一般被称为基于示例的思维链(few-shot CoT, Chain of Thought)Prompting。这个方向目前是 LLM 推理研究的主方向,很多工作都是在这个思路上做的,我们简单介绍几个效果显著的代表性工作,基本能代表 CoT 的技术发展方向。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-13.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 的主体思想其实很直白;为了教会 LLM 模型学会推理,给出一些人工写好的推理示例,示例里把得到最终答案前,一步步的具体推理步骤说清楚,而这些人工写的详细推理过程,就是思维链 Prompting,具体例子可参照上图中蓝色文字部分。CoT 的意思是让 LLM 模型明白一个道理;<strong>就是在推理过程中,步子不要迈得太大,否则很容易出错,改变思维模式,化大问题为小问题,步步为营,积小胜为大胜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>这个过程已经从 programming computer 过渡成 teaching computer 了,训练 AI 越来越像一个需要人教育培养的孩子。<br />\nCoT 其实就是给一些思维链 step by step 的 prompting</p>\n</blockquote>\n\n<p>最早明确提出 CoT 这个概念的文章是<a href=\"https://arxiv.org/pdf/2201.11903\">《Chain of thought prompting elicits reasoning in large language models》</a>,论文发布于 22 年 1 月份,虽然做法很简单,但是应用 CoT 后 LLM 模型的推理能力得到了巨大提升,GSM8K 数学推理测试集准确率提高到 60.1% 左右。当然,这种给出详细推理步骤和中间过程的思想,并非 CoT 最早提出的,更早一些的「scratchpad」技术(可参考<a href=\"https://arxiv.org/pdf/2112.00114\">《Show Your Work: Scratchpads for Intermediate Computation with Language Models》</a>)首先采用了类似的思路。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-14.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 提出不久,很快在 22 年 3 月份,一项被称为「Self-Consistency」的改进技术就将 GSM8K 测试集准确率提高到 74.4%,提出这项改进的论文是<a href=\"https://arxiv.org/pdf/2203.11171\">《Self-Consistency Improves Chain of Thought Reasoning in Language Models》</a>。「Self-Consistency」的思路也很直观(参考上图):首先可以利用 CoT 给出几个写了推理过程的示例,然后要求 LLM 对给定的问题进行推理,如果是 CoT,直接输出一个推理过程和答案,整个过程就结束了。「Self-Consistency」则不然,它要求 LLM 输出多个不同的推理过程和答案,然后采用投票的方式选出最佳答案,思路非常简单直接,但是效果也确实好。「Self-Consistency」其实是教导 LLM 学会这么一个道理:孔乙己说过茴香豆的「茴」字有四种写法,类似的,一个数学题的正确解法也可以有很多种,每个不同的推导过程都指向最终的答案。条条大路通罗马,虽说也有个别迷路走到北京的,但是迷路的毕竟是少数,看看大多数人走到哪里,哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义,是不是这道理?</p>\n\n<p>再往后,<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>这个工作在「Self-Consistency」基础上,进一步集成了「从一个 Prompt 问题拓展到多个 Prompt 问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票」这三个改进点,将 GSM8K 测试集准确率提高到 83% 左右。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>GSM8K 数学推理测试集 <br />\n1、提高到 40.4%:加一句「Let’s think step by step」 <br />\n2、提高到 60.1%:应用 CoT 后,即训练时给 LLM 几个写了推理过程的示例 <br />\n3、提高到 74.7%:基于 CoT 的改进技术 Self-Consistency,给出多个不同推理过程和答案,投票选出最好答案 <br />\n4、提高到 83% 左右:基于 Self-Constistenty 的改进技术,1)一个 Prompt 拓展为多个 Prompt;2)检查推理中间步骤正确性;3)对多个输出的回答加权投票。</p>\n</blockquote>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-15.jpeg\" alt=\"image\" /></p>\n\n<p>第三种思路体现了一种分治算法的思想。当然这个所谓「分治」是我归纳的,别人没这么说。这种思路的核心思想是:对于一个复杂的推理问题,我们把它分解成若干容易解决的子问题,一一解决掉子问题后,我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得,这种思路可能才是揭示问题本质、最终解决 LLM 复杂推理问题正宗的道路。我们以「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术为例来说明这种思路的一种具体实现方式,如上图所示:它分为两个阶段,第一个阶段,从原始问题我们可以得知最终要问的问题是什么,我们假设最终问题是 Final Q,然后从原始问题填充 Prompt 模版「如果要解决 Final Q 问题,那么我需要先解决」,然后把原始问题和这个 Prompt 交给 LLM,让 LLM 模型给出答案,等于让 LLM 给出最终问题的前置子问题 Sub Q;接下来我们进入第二个阶段,让 LLM 先回答刚才拿到的子问题Sub Q,并拿到对应的答案,然后原始问题拼接子问题 Sub Q 及对应答案,再去问 LLM 最终那个问题 Final Q,此时LLM会给出最后的答案。如此这般,体现出拆解子问题,并从子问题的答案逐步找出最终答案的思路。</p>\n\n<h3 id=\"2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</h3>\n\n<p>以上是目前利用 Prompt 激发 LLM 模型推理能力的三种主流做法,而关于 LLM 的推理能力,目前还观察到一个有趣且费解的现象:除了文本外,如果能够加入程序代码一起参与模型预训练,则能大幅提升 LLM 模型的推理能力。这个结论从不少论文的实验部分都可以得出(可以参考<a href=\"https://arxiv.org/pdf/2210.03493\">《AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS》</a>/<a href=\"https://arxiv.org/pdf/2210.09261\">《Challenging BIG-Bench tasks and whether chain-of-thought can solve them》</a>等论文的实验部分)。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-16.jpeg\" alt=\"image\" /></p>\n\n<p>上图给出了一份实验数据,来自于论文<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>,其中 GPT3 davinci 就是标准的 GPT 3 模型,基于纯文本训练;code-davinci-002(OpenAI 内部称为 Codex)是同时在 Code 和 NLP 数据上训练的模型。如果比较两者效果,可以看出,不论采用具体哪种推理方法,仅仅是从纯文本预训练模型切换到文本和 Code 混合预训练模型,在几乎所有测试数据集合上,模型推理能力都得到了巨大的效果提升,比如我们以「Self Consistency」方法为例,在大多数据集合上的性能提升,都直接超过了 20 到 50 个百分点,这是很恐怖的性能提升,而其实在具体推理模型层面,我们什么也没做,仅仅是预训练的时候除了文本,额外加入了程序代码而已。</p>\n\n<p>除了这个现象,从上图数据中,我们还可以得出其它一些结论,比如 GPT 3 这种纯文本预训练模型,其实是具备相当程度的推理能力的,除了在 GSM8K 这种数学推理上效果比较差外,其它推理数据数据集合表现也还可以,前提你需要采用合适的方法,来激发出它本身就具备的这种能力;再比如,text-davinci-002,也就是在 code-davinci-002 基础上加入 instruct fine-tuning 后的模型(就是加入 InstructGPT 或 ChatGPT 模型的第一步),其推理能力要弱于 Codex,但是有其它研究表明它在自然语言处理任务又要强于 Codex。而这貌似说明了,加入 instruct fine-tuning,会损害 LLM 模型的推理能力,但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的,也能启发后续进一步的思考和探索。</p>\n\n<p>那么,一个自然的疑问是:<strong>为何预训练模型可以从代码的预训练中获得额外的推理能力?确切原因目前未知,值得深入探索</strong>。我猜测可能是因为原始版本的 Codex(只使用代码训练,可参考文献:<a href=\"https://arxiv.org/pdf/2107.03374\">《Evaluating Large Language Models Trained on Code》</a>)的代码训练是从文本生成代码,而且代码中往往包含很多文本注释,本质上这类似于预训练模型做了 <文本,Code> 两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释,很明显这些数学类或逻辑推理类的数据,对于解决下游数学推理问题是有帮助的,我猜大概率原因在此。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么预训练模型可以从代码预训练中获得额外的推力能力?</p>\n</blockquote>\n\n<h3 id=\"3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</h3>\n\n<p>上面介绍了 LLM 推理的主流技术思路和现有的一些结论,接下来谈谈我对 LLM 模型推理技术的思考,以下内容纯个人推断,没有太多证据,还请谨慎参考。我的判断是:虽然最近一年来,关于激发 LLM 的推理能力,这方面的技术进展很快,也取得了很大的技术进步,但是总体感觉是,我们可能走在正确的方向上,但是距离接触到真正的问题本质还有一段距离,对此要有更深入的思考和探索。</p>\n\n<p>首先,我比较赞同上述分治算法的主体思路,对于复杂的推理问题,我们应该把它拆解成若干简单的子问题,因为子问题对于 LLM 来说回答正确的概率就大很多,让 LLM 一一 回答子问题后,再逐步推导出最终答案。受到「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术的启发,如果进一步思考,我觉得 LLM 推理本质上很可能会是如下两种可能的其中之一:不断和 LLM 进行交互的图上推理问题,抑或是不断和LLM进行交互的程序流程图执行问题。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-17.jpeg\" alt=\"image\" /></p>\n\n<p>先说图上推理问题,如上图所示,假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构,图中的节点是子问题或者子步骤,图中的边代表了子问题之间的依赖关系,就是说只有回答好子问题 A,才能回答子问题 B,而且图中大概率存在循环结构,就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图,那么可以根据依赖关系,引导 LLM 一步一步按照图结构,回答必须首先回答的子问题,直到推导出最终答案。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-18.jpeg\" alt=\"image\" /></p>\n\n<p>再说程序流程图问题,参考上图,假设我们有办法把复杂问题拆解成子问题或子步骤,并产生一个由子步骤构成的类似程序流程图的结构,在这个结构里,有些步骤会反复执行多次(循环结构),有些步骤的执行需要进行条件判断(条件分支)。总而言之,在执行每个子步骤的时候和 LLM 进行交互,得到子步骤的答案,然后按照流程不断执行,直到输出最终答案。类似这种模式。假设这个思路大致正确的话,也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力:大概率因为 <文本,代码> 的多模态预训练模型,在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁,将两者联系起来的,即由文本描述到隐含的流程图,再映射到由流程图产生具体的代码。也就是说,这种多模态预训练,可以增强 LLM 模型从文本构建出隐含的流程图并按照流程图执行的能力,也就是加强了它的推理能力。</p>\n\n<p>当然,上述思路最大的问题是,我们如何根据文本描述的问题,能够靠 LLM 模型,或者其它模型,得到图结构或者流程图结构?这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练,走隐式学习内部隐含结构的方法。而目前的 CoT 技术,如果套到上述思路来思考的话,可以这么理解:标准 CoT,其实就是靠自然语言文本来描述图结构或者程序流程图的;而「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术,则是试图根据最后一个图节点,靠倒推来试图推导出其中的图结构,但是很明显,目前的方法限制了它倒推的深度,也就是说它只能推导出非常简单的图结构,这正是限制它能力的所在。</p>\n\n<h2 id=\"六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</h2>\n\n<p>这里列出一些我个人认为比较重要的 LLM 研究领域,或值得深入探索的研究方向。</p>\n\n<h4 id=\"探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</h4>\n\n<p>尽管继续推大 LLM 模型的规模,这事看似没有技术含量,但是其实这个事情异常重要。我个人判断,自从 Bert 出现以来,到 GPT 3,再到 ChatGPT,大概率这些给人印象深刻的关键技术突破,核心贡献都来自于 LLM 模型规模的增长,而非某项具体技术。说不定,揭开 AGI 真正的钥匙就是:超大规模及足够多样性的数据、超大规模的模型,以及充分的训练过程。再者,做超大规模的 LLM 模型,对技术团队的工程实现能力要求是非常高的,也不能认为这事情缺乏技术含量。</p>\n\n<p>那么继续推大 LLM 模型规模,有什么研究意义呢?我觉得有两方面的价值。首先,如上所述,我们已知,对于知识密集型的任务,随着模型规模越大,各种任务的效果会越来越好;而对很多推理类型的有难度的任务,加上 CoT Prompting 后,其效果也呈现出遵循 Scaling law 的趋向。那么,很自然的一个问题就是:对于这些任务,LLM 的规模效应,能将这些任务解决到何种程度?这是包括我在内,很多人关心的问题。其次,考虑到 LLM 具备的神奇的「涌现能力」,如果我们继续增加模型规模,它会解锁哪些让我们意想不到的新能力呢?这也是很有意思的问题。考虑到以上两点,我们仍然需要不断增大模型规模,看看模型规模对解决各类任务的天花板在哪里。</p>\n\n<p>当然,这种事情也就只能说说,对 99.99% 的从业者来说,是没有机会和能力做这个事情的。要做这个事情,对研究机构的财力及投入意愿、工程能力、技术热情,都有极高的要求,缺一不可。能做这事情的机构,粗估下来,国外不超过 5 家,国内不超过 3 家。当然,考虑到成本问题,未来也许会出现「股份制大模型」,就是有能力的几家机构合作,群策群力,一起来共建超级大模型的现象。</p>\n\n<h4 id=\"增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</h4>\n\n<p>正如之前对 LLM 推理能力的叙述,尽管 LLM 在最近一年推理能力得到了很大的提升,但是很多研究(参考<a href=\"https://arxiv.org/pdf/2208.05051\">《Limitations of Language Models in Arithmetic and Symbolic Induction》</a>/<a href=\"https://arxiv.org/pdf/2206.10498\">《Large Language Models Still Can’t Plan》</a>)表明,目前 LLM 能够解决得比较好的推理问题,往往都相对简单,LLM 的复杂推理能力仍然薄弱,比如即使是简单的字符拷贝推理或者加减乘除运算,当字符串或者数字非常长的时候,LLM 推理能力会极速下降,再比如行为规划能力等复杂推理能力很弱。总而言之,加强 LLM 的复杂推理能力,应该是 LLM 未来研究中最重要的环节之一。</p>\n\n<p>前文有述,<strong>加入代码加入预训练,这是一种直接增强 LLM 推理能力的方向。这个方向目前研究尚显不足,更像是实践经验的总结</strong>,探索背后的原理,并进而引入更多类型除代码外的新型数据来增强 LLM 的推理能力,这可能是更本质提升推理能力的方向。</p>\n\n<h4 id=\"llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</h4>\n\n<p>目前的 ChatGPT 擅长 NLP 和 Code 任务,作为通向 AGI 的重要种子选手,<strong>将图像、视频、音频等图像与多模态集成进入 LLM,乃至 AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入 LLM</strong>,是 LLM 通往 AGI 的必经之路。而这个方向才刚刚开始,因此具备<strong>很高的研究价值</strong>。</p>\n\n<h4 id=\"更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</h4>\n\n<p>如前所述,ChatGPT 的最大技术贡献即在此。但是很明显,目前的技术并不完美,肯定还有很多命令 LLM 理解不了。所以,沿着这个方向,寻找更好的技术,<strong>来让人类使用自己习惯的命令表达方式,而 LLM 又能听懂,这是个新的,且非常有前景的技术方向</strong>。</p>\n\n<h4 id=\"建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</h4>\n\n<p>好的评测数据集,是引导技术不断进步的基石。随着 LLM 模型逐步增大,任务效果快速提升,导致很多标准测试集快速过时。也就是说,这些数据集合相对现有技术来说,太容易了,在没有难度的测试集合下,我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合,是促进 LLM 技术进步的关键所在。</p>\n\n<p>目前行业应出现了一些新的测试集,有代表性的包括 BIGBench、OPT-IML 等。这些测试集合体现出一些特性,比如相对 LLM 现有技术具备一定的难度、综合了各种各样多种类型的任务等。</p>\n\n<p>受到 ChatGPT 的启发,我觉得除此外应纳入另一考虑因素:体现真实用户需求。就是说,这些任务的表述由用户真实发起,这种方式构建出来的 LLM 模型,才能解决用户实际需求。</p>\n\n<p>除此外,相信 LLM 会快速将能力溢出到 NLP 之外的领域,而如何融入更多其它领域的评测数据,也是需要提前去考虑。</p>\n\n<h4 id=\"高质量数据工程\">高质量数据工程</h4>\n\n<p>对于预训练模型来说,数据是其根本,预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此,我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。</p>\n\n<p>关于数据,需要考虑两个方面:数据的质量和数量。而根据 T5 的对比实验,我们可以得出结论:在数量和质量两个因素里,质量优先,正确的道路应该是在保证数据质量的前提下,再去增大数据规模。</p>\n\n<p>数据质量,包括数据的信息含量以及数据的多样性等多个衡量标准,比如 Wiki 明显就属于世界知识密度极高的高质量数据,这是从信息含量来说的;而增加数据类型的多样性,无疑是激发 LLM 各种新能力的根本,比如加入问答网站的数据,对于 LLM 的 QA 能力提升是有直接帮助的。多样化的数据赋予了 LLM 更好解决更多不同类型任务的能力,所以,这可能是数据质量里最关键的标准。</p>\n\n<p>关于数据数量,原则上互联网上公开发布的数据都可以纳入 LLM 模型的预训练过程。那么,它的极限在哪里?<a href=\"https://arxiv.org/pdf/2211.04325\">《Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning》</a>对此进行了估算,结论是到 2026 年左右,高质量的NLP数据将会用光,低质量 NLP 数据会在 2030 到 2050 年用光,而低质量图像数据会在 2030 到 2060 年用光。而这意味着:要么到时我们有新类型的数据源,要么我们必须增加 LLM 模型对数据的利用效率。否则,目前这种数据驱动的模型优化方式将会停止进步,或者收益减少。</p>\n\n<h4 id=\"超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</h4>\n\n<p>目前规模最大的 LLM 中,有相当比例的模型采取了稀疏(Sparse)结构,比如 GPT 3、PaLM、GLaM 等,GPT 4 大概率也会走稀疏模型路线。之所以采用 Sparse 化的模型,主要好处是它可以极大减少 LLM 的训练时间和在线推理时间。Switch Transformer 论文里指出:在相同算力预算的前提下,使用稀疏化 Transformer,相对 Dense Transformer,LLM 模型的训练速度可以提升 4 倍到 7 倍。为何 Sparse 模型可以加快训练和推理时间呢?这是因为尽管模型参数巨大,但是对于某个训练实例,Sparse 模型通过路由机制,只使用整个参数中的一小部分,参与训练和推理的活跃参数量比较少,所以速度快。</p>\n\n<p>我认为未来超大的 LLM 模型大概率会收敛到稀疏模型。主要有两个原因:一方面,现有研究表明(参考<a href=\"https://arxiv.org/pdf/2210.06313\">《Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers》</a>),标准的 Dense Transformer在训练和推理时,它本身也是稀疏激活的,就是说只有部分参数会被激活,大部分参数没有参与训练和推理过程。既然这样,我们不如直接迁移到稀疏模型;另外,毫无疑问 LLM 模型的规模会继续推大,而高昂的训练成本是妨碍其进一步扩大模型的重要阻力,<strong>使用稀疏模型可以极大降低超大模型的训练成本</strong>,所以随着模型规模越大,稀疏模型带来的收益越明显。考虑到这两个方面,大概率未来更大的 LLM 模型会采用稀疏模型方案。</p>\n\n<p>那为何目前其它大规模模型不走稀疏模型的路线呢?因为 Sparse 模型存在训练不稳定、容易过拟合等问题,不太容易训练好。所以,如何修正稀疏模型面临的问题,<strong>设计出更容易训练的稀疏模型,是很重要的未来研究方向</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>重要研究拓展方向 <br />\n1、任务层 —— 增加 LLM 推理能力 <br />\n2、接口层 —— 命令更自然:怎么把 ICL 逐渐过渡成 zero-shot 的 Instruct <br />\n3、任务层 —— 多模态拓展:先在 NLP 和 Code 上奔向 AGI,把 CV、音频、AI for science、自动驾驶、机器人逐渐囊括进来 <br />\n4、模型层 —— 训练更容易:稀疏矩阵问题很多(训练不稳定、容易过拟合)的解决</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>为什么稀疏结构效果好?<br />\n1、计算快:如果很稠密,则对很多知识的提炼都耦合在了一起。如果比较稀疏,就类似人脑,对不同功能、不同知识等内容是分开存储的,耦合少、计算速度就快。<br />\n2、好训练:越稀疏训练成本越低\n3、缺点:训练不稳定,容易过拟合</p>\n</blockquote>\n\n<h2 id=\"七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</h2>\n\n<p>如果希望能复刻类似 ChatGPT 这种效果令人惊艳的 LLM 模型,综合目前的各种研究结论,在做技术选型时需要重点权衡如下问题:</p>\n\n<p>首先,在预训练模式上,我们有三种选择:GPT 这种自回归语言模型,Bert 这种双向语言模型,以及 T5 这种混合模式(Encoder-Decoder 架构,在 Encoder 采取双向语言模型,Decoder 采取自回归语言模型,所以是一种混合结构,但其本质仍属于 Bert 模式)。我们应选择 GPT 这种自回归语言模型,其原因在本文范式转换部分有做分析。目前看,<strong>国内 LLM 在做这方面技术选型的时候,貌似很多都走了 Bert 双向语言模型或 T5 混合语言模型的技术路线,很可能方向走偏了</strong>。</p>\n\n<p>第二,<strong>强大的推理能力是让用户认可 LLM 的重要心理基础</strong>,而如果希望 LLM 能够具备强大的推理能力,根据目前经验,最好在做预训练的时候,要引入大量代码和文本一起进行 LLM 训练。至于其中的道理,在本文前面相关部分有对应分析。</p>\n\n<p>第三,如果希望模型参数规模不要那么巨大,但又希望效果仍然足够好,此时有两个技术选项可做配置:要么增强高质量数据收集、挖掘、清理等方面的工作,意思是我模型参数可以是 ChatGPT / GPT 4 的一半,但是要想达到类似的效果,那么高质量训练数据的数量就需要是 ChatGPT/GPT 4 模型的一倍(Chinchilla 的路子);另外一个可以有效减小模型规模的路线是采取文本检索(Retrieval based)模型 + LLM 的路线,这样也可以在效果相当的前提下,极大减少 LLM 模型的参数规模。这两个技术选型不互斥,反而是互补的,也即是说,可以同时采取这两个技术,在模型规模相对比较小的前提下,达到超级大模型类似的效果。</p>\n\n<p>第四,超级大模型因为模型规模大,所以训练成本过高,导致很少有机构有能力去做这件事。而且由上文分析可见,继续不断推大 LLM 模型规模是肯定会发生、也应该去做的事情。于是,如何通过技术手段降低 LLM 的训练成本就很重要。LLM 的特征抽取器 Sparse 化是有效降低模型训练及推理成本的技术选择。由此可见,随着模型越来越大,LLM 模型 Sparse 化是一个应该考虑的选项。</p>\n\n<p>第五,ChatGPT 是目前最接近理想 LLM 的技术方案,而理想中的 LLM 应该是以一个几乎无所不能的基础通用大模型作为依托,来支持各种各样的上层任务类型。目前看,支持越来越多的任务类型,主要是通过增加 LLM 预训练数据的多样性来达成的,数据多样性越好,LLM 能够支持的任务类型就越丰富。所以,<strong>应该重视通过增加数据多样性来增加 LLM 新能力的思路</strong>。</p>\n\n<p>第六,易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务,而 LLM 要能够理解这些 Instruct 的真实含义。另外,也要注意这些 Instruct 是符合人类真实需求的,就是说,要从最终用户那里收集任务表述方式,而不能靠研发人员自己的臆想或猜测。ChatGPT 给我最大的启发其实是这一点,至于是否用增强学习我倒觉得不重要,其它替代技术应该也能做类似的事情。</p>\n\n<h2 id=\"八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</h2>\n\n<p>为什么是 OpenAI 作出了 ChatGPT,而不是其它机构呢?我们在这里可以做个简单分析。</p>\n\n<p>在本文开头,我们提到了 OpenAI 看待 LLM 的理念。OpenAI 是怎么看待 LLM 的呢?回顾它不断推出的技术,可以看出,它其实从 GPT 1.0 开始,基本就坚定地把 LLM 看做是通往 AGI 的一条必由之路。具体而言,在 OpenAI 眼中,未来的 AGI 应该长这个样子:有一个任务无关的超大型 LLM,用来从海量数据中学习各种知识,这个 LLM 以生成一切的方式,来解决各种各样的实际问题,而且它应该能听懂人类的命令,以便于人类使用。其实对 LLM 发展理念的理解,在前半部分,就是「构建一个任务无关的超大型 LLM,让它从海量数据中学习各种知识」,这一点几乎是大家的共识,能体现出 OpenAI 眼光的其实是后半部分。</p>\n\n<p>OpenAI 的理念比较超前,对自我定位从一开始就定得比较高,始终坚定不移地探索上述方式是否可以实现 AGI。OpenAI 之所以能作出 ChatGPT,胜在一个是定位比较高,另一个是不受外界干扰,态度上坚定不移。</p>\n\n<p>我们可以回顾下它走的一些关键路程:GPT 1.0 走的是生成模式的自回归语言模型路线,比 Bert 出来的还早些。Bert 证明了:双向语言模型对于很多 NLP 理解类任务,效果比自回归这种单向语言模型效果更好。尽管如此,GPT 2.0 并没有因此切换到双向语言模型这条路上,仍然走文本生成的路,而且开始尝试零示例(zero shot)prompt 和少量示例(few shot)prompt。其实这时候, OpenAI 心目中的 AGI 已经开始浮出水面,逐渐显示出轮廓了。只是因为 zero shot/few shot 效果比 Bert+fine-tuning 差的比较远,所以大家都没太当回事,甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候,我估计即使是 OpenAI 自己,也不一定能确保这条路肯定能走通。</p>\n\n<p>但是,这不妨碍它继续在这条路上往后走。GPT 3.0 已经展示出了比较强大的 zero shot/few shot prompt 能力,这时候 OpenAI 心目中的 AGI 已经完全漏出水面,轮廓清晰,而且它的效果也证明了这条路,是有较大可能走得通的。GPT 3.0 是一个决定 LLM 发展方向的叉路口和分水岭,与之对应的另外一条路是「Bert+fine-tuning」模式。在这个岔路口,不同的从业者选择走上了不同的道路,后面的技术差距也是从这里开始拉开的。很遗憾地是,国内很多从业者选择继续在「Bert+fine-tuning」这条路上往后走,这也是造成今天落后局面的一个关键时间节点。再往后,就是 InstructGPT 和 ChatGPT 了,OpenAI 通过 ChatGPT 证明了一点;虽然我们距离真正的 AGI,可能还有很长的路要走,但是通过超大 LLM 走向 AGI 这条路,目前看是可行的。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>自然语言处理 AIGC 近年的发展脉络、关键论文、技术里程碑和商业应用</title>\n \t<meta name=\"description\" content=\"火出圈的 ChatGPT,背后是自然语言处理领域近几年发展的成果。本文从近几年自然语言处理的关键发展脉络,过程中关键的几篇学术论文,这几年的所有重要行业里程碑,以及目前为止业内已经诞生的应用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>自然语言处理 AIGC 近年的发展脉络、关键论文、技术里程碑和商业应用</h2>\t\t\n\t<time datetime=\"2022-12-24T15:08:01+00:00\" class=\"by-line\">24 Dec 2022, 杭州 | 麦克船长 | 总计 9713 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一自然语言处理领域近年的发展关键节点\" id=\"markdown-toc-一自然语言处理领域近年的发展关键节点\">一、自然语言处理领域近年的发展关键节点</a> <ul>\n <li><a href=\"#1从理性主义到经验主义\" id=\"markdown-toc-1从理性主义到经验主义\">1、从理性主义到经验主义</a></li>\n <li><a href=\"#2经验主义的早期还不是深度学习\" id=\"markdown-toc-2经验主义的早期还不是深度学习\">2、经验主义的早期,还不是深度学习</a></li>\n <li><a href=\"#3撇开特征让机器囫囵吞枣地学吧\" id=\"markdown-toc-3撇开特征让机器囫囵吞枣地学吧\">3、撇开特征,让机器「囫囵吞枣」地学吧</a></li>\n <li><a href=\"#4囫囵个儿地学习省去特征工程的人工但也少不了标注的人工\" id=\"markdown-toc-4囫囵个儿地学习省去特征工程的人工但也少不了标注的人工\">4、囫囵个儿地学习,省去特征工程的人工,但也少不了标注的人工</a></li>\n <li><a href=\"#5自监督学习法让我们省去人工标注\" id=\"markdown-toc-5自监督学习法让我们省去人工标注\">5、自监督学习法,让我们省去人工标注</a></li>\n <li><a href=\"#6用原始的任务训练出来的模型能迁移去解决新任务吗\" id=\"markdown-toc-6用原始的任务训练出来的模型能迁移去解决新任务吗\">6、用原始的任务训练出来的模型,能迁移去解决新任务吗?</a></li>\n <li><a href=\"#7从理解到生成nlp-是最直面-aigc-最硬核难题的领域\" id=\"markdown-toc-7从理解到生成nlp-是最直面-aigc-最硬核难题的领域\">7、从理解到生成,NLP 是最直面 AIGC 最硬核难题的领域</a></li>\n <li><a href=\"#8数据和算力有了还不够\" id=\"markdown-toc-8数据和算力有了还不够\">8、数据和算力有了,还不够</a></li>\n </ul>\n </li>\n <li><a href=\"#二学术里程碑几篇重量级论文\" id=\"markdown-toc-二学术里程碑几篇重量级论文\">二、学术里程碑:几篇重量级论文</a> <ul>\n <li><a href=\"#0提出-attention-机制的neural-machine-translation-by-jointly-learning-to-align-and-translate2015\" id=\"markdown-toc-0提出-attention-机制的neural-machine-translation-by-jointly-learning-to-align-and-translate2015\">0、提出 Attention 机制的《Neural Machine Translation by Jointly Learning to Align and Translate》(2015)</a></li>\n <li><a href=\"#1提出-transformer-的attention-is-all-you-need2017\" id=\"markdown-toc-1提出-transformer-的attention-is-all-you-need2017\">1、提出 Transformer 的《Attention is All You Need》(2017)</a></li>\n <li><a href=\"#2elmo-deep-contextualized-word-representations\" id=\"markdown-toc-2elmo-deep-contextualized-word-representations\">2、ELMo: Deep contextualized word representations</a></li>\n <li><a href=\"#3gpt-1improving-language-understanding-by-generative-pre-training\" id=\"markdown-toc-3gpt-1improving-language-understanding-by-generative-pre-training\">3、GPT-1:Improving Language Understanding by Generative Pre-Training</a></li>\n <li><a href=\"#4bert-pre-training-of-deep-bidirectional-transformers-for-language-understanding2018\" id=\"markdown-toc-4bert-pre-training-of-deep-bidirectional-transformers-for-language-understanding2018\">4、BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding(2018)</a></li>\n <li><a href=\"#5gpt-2\" id=\"markdown-toc-5gpt-2\">5、GPT-2:</a></li>\n <li><a href=\"#6gpt-3-language-models-are-few-shot-learners2020\" id=\"markdown-toc-6gpt-3-language-models-are-few-shot-learners2020\">6、GPT-3: Language Models are Few-Shot Learners(2020)</a></li>\n <li><a href=\"#7instructgpt\" id=\"markdown-toc-7instructgpt\">7、InstructGPT</a></li>\n <li><a href=\"#其他的重量级论文\" id=\"markdown-toc-其他的重量级论文\">其他的重量级论文</a></li>\n </ul>\n </li>\n <li><a href=\"#三行业里程碑\" id=\"markdown-toc-三行业里程碑\">三、行业里程碑</a></li>\n <li><a href=\"#四成本\" id=\"markdown-toc-四成本\">四、成本</a></li>\n <li><a href=\"#五业内应用\" id=\"markdown-toc-五业内应用\">五、业内应用</a></li>\n <li><a href=\"#五行业内哪些人的言论值得我们日常重点关注\" id=\"markdown-toc-五行业内哪些人的言论值得我们日常重点关注\">五、行业内哪些人的言论值得我们日常重点关注</a></li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<h3 id=\"一自然语言处理领域近年的发展关键节点\">一、自然语言处理领域近年的发展关键节点</h3>\n\n<p><img src=\"/img/src/2022-12-17-ai-bert-1-1.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"1从理性主义到经验主义\">1、从理性主义到经验主义</h4>\n\n<p>自然语言处理(Natural Language Processing,简称 NLP),一开始走的是专家路线,也就是想「白盒化」来解构对自然语言的理解,这被称为「符号主义(Symbolism)」。符号主义的背后,是人类对自己用符号系统基于逻辑来完全数字化自然语言的自信。反正这条路目前是没走出来,你要非说「这其实是自负」,暂时人工智能专家们也无可辩驳。沿着这个路径的研究一直占据人工智能主流到 20 世纪 90 年代。</p>\n\n<p>这里我们想想,自然语言处理,其实是两个过程,一个是输入,即对自然语言的理解,一个是输出,即近期有点火的概念 AIGC(Artificial Intelligence Generated Content)。我们这里说说前者,人类学习语言的过程,哪有什么符号系统,哪有什么逻辑,就是被疯狂输入,然后经过很多个月之后,一个小 baby 就学会说话了,这个过程没有「理性主义」的痕迹,只有「经验主义」的胜利。那么 AI 学人话,能这样吗?</p>\n\n<p>于是就有了所谓「联结主义(Connectionism)」:你知道人的神经元网络吧?这个是一个个神经元,相互联结组成一个网络,通过这个网络来非常「黑盒化」地学习自然语言。至于这个网络里的每一个细节,我们不甚清楚,但就是可以通过这个网络模型学会自然语言,这就是一种「经验主义」。从 20 世纪 90 年代,人工智能领域就是沿着这个方向取得了巨大进展的。要注意一点,经验主义地路径解决 NLP 问题,并不等同于神经网络,但它是目前最有效的。</p>\n\n<h4 id=\"2经验主义的早期还不是深度学习\">2、经验主义的早期,还不是深度学习</h4>\n\n<p>最初的经验主义,还是主要通过人工对特征进行「经验性地」提取,对计算机来说不要让它求甚解,直接给它喂这些梳理好的「特征」就好了。而这个需要一定的专业领域知识储备,加上人工地提取特征的操作过程,被称为「特征工程」。</p>\n\n<p>可以看出来,「特征工程」的人工工作量非常大,可以说是名副其实的「人工」智能了(此处捂脸)。但这已经比此前的、有点理想的那种构建符号系统的想法,要务实多了,也确实在解决问题的实用主义上也好得多。以这个为主流的研究,大概持续到 2010 年代。</p>\n\n<h4 id=\"3撇开特征让机器囫囵吞枣地学吧\">3、撇开特征,让机器「囫囵吞枣」地学吧</h4>\n\n<p>要经过「人工」对特征进行研究、提取,实在是太难了,你说是「经验主义」,其实我个人认为有点介于「理性主义」与「经验主义」之间。毕竟还是非常需要人进行非常专家级地梳理的。于是,更囫囵个儿地给机器喂数据,让机器学会的方向,逐渐成为主流。能这样的前提,是牛逼算力的大发展,以及海量数据集的大规模沉淀,所以才会在 2010 年代爆发。</p>\n\n<p>这囫囵吞枣的学法,目前主要都是基于深度神经网路的表示学习方法实现的。为啥说「深度神经网络」,因为「从输入到输出」是有一层又一层的神经网络,第一层接收原始的自然语言输入,这么多层的神经网络就被称为深度神经网络。这个过程显著地避免了「特征工程」的人工高成本。</p>\n\n<h4 id=\"4囫囵个儿地学习省去特征工程的人工但也少不了标注的人工\">4、囫囵个儿地学习,省去特征工程的人工,但也少不了标注的人工</h4>\n\n<p>虽然省去了需要专家的「特征工程」,但是这个「囫囵个儿学习法」还是需要依赖标注数据的,也就是「监督学习」。通过先学习大量有人工标注地数据,构建好深度神经网络后,再对测试数据进行验证,最后再用于使用。能不能把人工标注也给省了?或者至少不需要海量标注吧。</p>\n\n<h4 id=\"5自监督学习法让我们省去人工标注\">5、自监督学习法,让我们省去人工标注</h4>\n\n<p>大家上中学的时候做过英语试卷里的「完形填空」吗?为什么我们根据一个填空的上下文,能推测出这个空应该填什么词?那我们是不是可以根据这个原理,把一段段完整的文字内容挖词进行训练学习?没错,这个挖掉的词,就可以当做曾经的「人工标注」,上年文就是训练数据。但是需要海量的数据,怎么办?</p>\n\n<p>好在书籍、互联网网页是我们最好的数据来源,而且数据量极其巨大,于是这就解决了人工个标注问题。由此衍生出来的方法,就被成为「自监督学习(Self-Supervised Learning)」。</p>\n\n<h4 id=\"6用原始的任务训练出来的模型能迁移去解决新任务吗\">6、用原始的任务训练出来的模型,能迁移去解决新任务吗?</h4>\n\n<p>这是一个迁移学习问题,这也就引出了「预训练(Pre-Training)」,最近火到出圈的「ChatGPT」最后两个字母「PT」就是「预训练」。正如「预训练」这个名字,我们先对一些原始任务用大量数据对一个模型进行训练(这个过程其实就叫预训练),然后对于实际要解决的各种任务,再使用少量数据对模型进行精调(Fine-Tune),从而得到一个解决具体问题的模型。</p>\n\n<p>这样的方式,让面对具体任务(可以叫下游任务,或者目标任务)时可以省去很多训练,所以对这种模型叫做「预训练模型」。因此上游任务的训练,就变得非常有复用性、通用性价值,而不是每次面对新任务构建新模型来训练。沿着预训练模型,NLP 取得了非常多的突破。这个技术趋势,是从 2017 年 Transformer 模型在论文《Attention is All You Need》被提出后开始的,在论文中作者使用了大量的未标记的语言数据进行自监督学习,以学习 Transformer 模型的语言表示。然后,在这个自监督学习的模型的基础上,再使用少量的标记数据进行进一步训练,以解决具体的目标任务。</p>\n\n<h4 id=\"7从理解到生成nlp-是最直面-aigc-最硬核难题的领域\">7、从理解到生成,NLP 是最直面 AIGC 最硬核难题的领域</h4>\n\n<p>我们再说回到前面提到的人工标注,从这点来理解所谓「任务」。人工标注,是主观性很强的。在图像处理、语音识别两个领域,标注数据的复用性很强,所以可以积累大的数据标注集,这是有积累沉淀价值的,比如 CV 领域鼎鼎大名的 ImageNet 图像数据集。但是 NLP 领域的任务复杂、多样,很难像图像处理、语音识别那样单纯地得到大量有价值标注。什么意思呢?这与我们在不同领域面对的任务有关。</p>\n\n<p>比如给一副画,对于绝大多数需要输入这幅画的任务来说,标注出它是一副油画、作者梵高、画中有星空等等,都是必须的。比如对于一个人脸识别,哪里是眼睛、鼻子、嘴巴,也是从任务层面非常通用的。语音识别就更有通用性了。但是对于一句自然语言,一个随机的任务需要什么信息,这非常难以沉淀通用。</p>\n\n<p>从这个角度说,一个「图像处理」任务一般是要输出这个图像里有什么内容,一个「语音识别」任务一般是要输出这段语音的文字内容是什么。但是一个「自然语言处理」任务一般是要干嘛?鬼知道要干嘛,但肯定大多数时候是要先生成一段话作为回应,这也就是「自然语言生成」。</p>\n\n<p>所以 NLP 领域的 NLG(Natural Language Generation)面对着最多可能性的任务,也就是最直面 AIGC 核心问题的领域。</p>\n\n<h4 id=\"8数据和算力有了还不够\">8、数据和算力有了,还不够</h4>\n\n<p>我个人认为,预训练这个方向之所以正确,就是因为它在推动 AGI(Artificial General Intelligent)。这背后是一个基本哲学问题:我们应该把劲儿使在推动 AGI,还是应该认为每个领域都应该有自己独有的模型?</p>\n\n<p>这个问题的答案,在我看来是笃定的。AI 目前面对的还是人类思考的问题,而人面对的问题去构建的人脑学习模型,并没有呈现出在不同领域里人脑的学习方式有显著差异,更何况计算机能容纳的学习能力显然更广、更深。因此我很笃定,我们一定是要构建 AGI,为什么 AGI 将解决我们方方面面的问题。</p>\n\n<p>那么一个预训练模型,在下游能解决的问题越广,越说明这是在构建 AGI。但是反过来对上游的预训练模型的要求,就是它最好模型参数越多越好,这样能容纳的下游任务也就可能越多样。因此我们现在知道的 ChatGPT 背后的 OpenAI 公司此前研发的 GPT-3 已经有 1750 亿个参数了,这就是 —— 大模型。</p>\n\n<p>所以目前沿着预训练方向发展的自然语言处理领域,已经进入了「大模型、大数据、大算力」时代。</p>\n\n<h3 id=\"二学术里程碑几篇重量级论文\">二、学术里程碑:几篇重量级论文</h3>\n\n<p>以下重量级的论文,每一篇都不短,B 站上有一些二手解读,虽然二手但是也值得高效地看下,这些论文我罗列如下。我的理解也不深,欢迎随时交流。</p>\n\n<h4 id=\"0提出-attention-机制的neural-machine-translation-by-jointly-learning-to-align-and-translate2015\">0、提出 Attention 机制的《Neural Machine Translation by Jointly Learning to Align and Translate》(2015)</h4>\n\n<p>Bahdanau 等人在 2015 年提出了 Attention 机制,论文地址:<a href=\"https://arxiv.org/pdf/1409.0473.pdf\">https://arxiv.org/pdf/1409.0473.pdf</a></p>\n\n<h4 id=\"1提出-transformer-的attention-is-all-you-need2017\">1、提出 Transformer 的《Attention is All You Need》(2017)</h4>\n\n<p>论文地址:<a href=\"https://arxiv.org/pdf/1706.03762.pdf\">https://arxiv.org/pdf/1706.03762.pdf</a></p>\n\n<p>Google 的 Lamda、BERT,OpenAI 的 GPT-3 都是基于 Transformer 的。</p>\n\n<p>《Attention is all you need》是一篇颇具影响力的自然语言处理(NLP)论文,由 Google 在 2017 年发表。这篇论文提出了一种叫做 Transformer 的模型架构,这种模型架构不依赖于递归神经网络(RNN)或卷积神经网络(CNN)等传统的深度学习架构,而是使用了注意力机制(attention mechanism)和多头注意力(multi-head attention)来捕捉序列间的依赖关系。</p>\n\n<p>看到有人说「<strong>Transformer 基本宣告了 LSTM 在 NLP 领域的终结</strong>」。Transformer 模型在 NLP 领域内获得了广泛的应用,并且因为其较好的并行化能力,在计算资源有限的情况下也能够获得较好的性能。Transformer 模型也被广泛应用于其他领域,如计算机视觉、音频处理等。</p>\n\n<h4 id=\"2elmo-deep-contextualized-word-representations\">2、ELMo: Deep contextualized word representations</h4>\n\n<p>ELMo 是 Embeddings from Language Models 的缩写,刚好是《芝麻街》中一个角色的名字,是在 Peters 等人于 2018 年在 ACL(美国计算机学会计算语言学会议,NLP 领域顶级会议之一)上发表的论文《Deep contextualized word representations》中被提出来的。</p>\n\n<p>ELMo 是一种预训练模型,基于深度双向递归神经网络(biLSTM),可以用来生成词嵌入(word embeddings)。ELMo 使用了大量未标记的文本数据训练,并使用了多层双向递归神经网络来学习。</p>\n\n<h4 id=\"3gpt-1improving-language-understanding-by-generative-pre-training\">3、GPT-1:Improving Language Understanding by Generative Pre-Training</h4>\n\n<h4 id=\"4bert-pre-training-of-deep-bidirectional-transformers-for-language-understanding2018\">4、BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding(2018)</h4>\n\n<p>BERT 模型是在一篇于 2018 年发表的叫做《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》的论文中被提出来的,BERT 是 Bidirectional Encoder Representations from Transformers 的缩写。我觉得这个名字有点硬凑出来的意思,BERT 也是《芝麻街》里一个角色的名字,我想就是为了跟 ELMo 凑一块儿怕它孤单吧。这篇论文带来的最大突破性变化有:</p>\n\n<ul>\n <li>在语言模型预训练中引入双向信息:传统的预训练语言模型(比如 word2vec、GloVe)通常只考虑了单向的信息(前面的词语)。BERT 模型则同时考虑了前后的词语,从而更好地捕捉句子的上下文信息。</li>\n <li>在预训练中引入自监督学习任务。</li>\n</ul>\n\n<p>关于 BERT,我这里写了一篇背景介绍、用例试跑、优劣势分析:<a href=\"https://www.mikecaptain.com/2022/12/17/ai-bert-1/\">《你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例》</a></p>\n\n<h4 id=\"5gpt-2\">5、GPT-2:</h4>\n\n<h4 id=\"6gpt-3-language-models-are-few-shot-learners2020\">6、GPT-3: Language Models are Few-Shot Learners(2020)</h4>\n\n<p>这篇来自 OpenAI 的论文,提出了「小样本学习(Few-Shot Learning,FSL)」的新训练方法,可以在小样本的情况下取得优秀的表现。</p>\n\n<h4 id=\"7instructgpt\">7、InstructGPT</h4>\n\n<h4 id=\"其他的重量级论文\">其他的重量级论文</h4>\n\n<ul>\n <li>Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context(2019)</li>\n <li>RoBERTa: A Robustly Optimized BERT Pretraining Approach(2019)</li>\n <li>T5: Exploring the Limits of Transfer Learning witha Unified Text-to-Text Transformer(2020)</li>\n <li>ViT: An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale(2021)</li>\n <li>ERNIE-ViL: Vision and Language Pre-training for Image Captioning and VQA(2021)</li>\n <li>……</li>\n</ul>\n\n<h3 id=\"三行业里程碑\">三、行业里程碑</h3>\n\n<p>2017 年 8 月,Andrej Karpathy 在其 Twitter 上发文称「很遗憾,梯度下降(实现的 AI 模型)代码写得比你好」。同年 11 月 Andrej 在博客上表示,软件 2.0 将会区别于软件 1.0 时代,程序将由更抽象的、基于神经网络权重的程序语言编写。</p>\n\n<p>2018 年 OpenAI 推出了无监督的、基于强化学习的第一代 GPT。</p>\n\n<p>2019 年情人节,OpenAI 发布 GPT-2,当时被称为史上最强的「通用」自然语言处理模型,基于 Transformer,拥有 15 亿个参数,使用含有 800 万网页内容的数据集训练。</p>\n\n<p>2020 年 6 月,拥有 1750 亿个参数的 GPT-3 面世,这个模型的训练量是 GPT-2 的十倍不止,并开放了商业化 API 共使用,不到一年时间发展出约 300 家企业客户。</p>\n\n<p>2021 年 1月,Google 推出 Switch Transformer 模型,参数量 1.6 万亿,是人类首个万亿级参数的语言模型。</p>\n\n<p>2021 年 6 月,微软与 OpenAI 共同推出代码辅助生成 AI 工具 GitHub Copilot.</p>\n\n<p>2022 年 1 月,OpenAI 发布基于 GPT-3 微调的模型 InstructGPT(包括 text-davinci-001、text-davinci-002、text-davinci-003),微调主要来自于 RLHF(Reinforcement Learning via Human Feedback)。</p>\n\n<p>2022 年 5 月,杭州 AI 领域初创公司「感知阶跃(ZMO.ai)」宣布完成由高瓴资本领投、GGV Capital 和 GSR Ventures 跟投的 800 万美元 A 轮融资。</p>\n\n<p>2022 年 10 月 19 日,Jasper.ai 宣布完成由 Insight Partner 领投,Coatue、(BVP)Bessemer 以及 IVP 等机构跟投的 1.25 亿美元 A 轮融资,估值达到了 15 亿美元,Jasper AI 从产品上线至今仅 18 个月。</p>\n\n<p>2022 年 11 月底,OpenAI 推出基于 GPT-3.5 的 ChatGPT 对话系统,震惊全球。项目地址:https://chat.openai.com 。</p>\n\n<p>2022 年 12 月底,专注于各 AI 闭源项目的逆向工程的 Philip Wang 发布了 PaLM+RLHF 的文本生成开源模型,类似于 ChatGPT。该项目基于 Google 的大型语言模型 PaLM 和带有人类反馈的强化学习(RLHF),拥有 5400 亿个参数。项目地址:https://github.com/lucidrains/PaLM-rlhf-pytorch 。</p>\n\n<h3 id=\"四成本\">四、成本</h3>\n\n<p>目前成本主要有三方面:大模型、大数据、大算力。这其中最昂贵的成本首先是算力。下面有几个数据可以作为参照:</p>\n\n<ul>\n <li>2020 年的一项研究表明,开发一个只有 15 亿个参数的文本生成模型的费用高达 160 万美元。</li>\n <li>2022 年 7 月,为了训练拥有 1760 亿个参数的开源模型 Bloom,Hugging Face 的研究人员耗时三个月,使用了 384 个英伟达 A100 GPU。</li>\n <li>OpenAI 的文本生成 GPT-3(具有大约 1750 亿个参数)的运行成本约为每年 87,000 美元。</li>\n <li>Hugging Face 训练 Bloom 花了三个月的时间。</li>\n</ul>\n\n<h3 id=\"五业内应用\">五、业内应用</h3>\n\n<p>因为图片生成的容错率非常高,也就是在应用上的包容度更高,相比之下文本或语音的生成,是对结果容错非常低的,比如不容许事实错误、逻辑错误等等。这类的应用,我们能想到:</p>\n\n<ul>\n <li>虚拟客服(可以乱真的)</li>\n <li>智能助理:AI 家庭教师、AI 非诉律师、AI 医生助手、AI 新闻编辑、AI 设计助理</li>\n <li>智能翻译</li>\n <li>智能导购员:如果叠加虚拟人技术、语音合成技术,可以应用于电商</li>\n <li>AI 广告公司:替代传统广告公司</li>\n <li>AI 程序员助手:更高智能的辅助代码生成</li>\n <li>部分场景下的美术工作者:游戏素材生成、海报生成</li>\n</ul>\n\n<p>我们可以看到,AI 带来的这一波机会,都是曾经常说的「人不会被 AI 替代」的领域,也就是一些创作创意创新型工作,其中的中低端部分会因为成本因素而极力推动 AI 应用的发展。</p>\n\n<p>所以下面除了大家耳熟能详的 CV 领域的 AIGC 产品 Disco Diffusion、MidJourney、DALL·E 2、Stable Diffusion 之外,我们重点关注非图片生成类的应用。</p>\n\n<ul>\n <li>用于营销场景的 AI 写手与图像生成工具「<strong>Jasper.ai</strong>」,常被用于生成互联网营销文案(比如用于 Instagram、Tik Tok、Facebook、博客、email、论坛帖子 等等)。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-7.png\" alt=\"image\" /></p>\n\n<ul>\n <li>2021 年 6 月,微软与 OpenAI 共同推出的的代码辅助生成 AI 工具「<a href=\"https://github.com/features/copilot\">GitHub Copilot</a>」发布。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-2.jpg\" alt=\"image\" /></p>\n\n<ul>\n <li>文案神器「<strong>Copy.ai</strong>」:</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-9.png\" alt=\"image\" /></p>\n\n<ul>\n <li>虚拟客服「<strong>DialogFlow</strong>」,能理解电话、语音内容等输入,并且给出文本或语音合成的输出。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-8.png\" alt=\"image\" /></p>\n\n<ul>\n <li>2021 年年底,西湖心辰公司发布「<a href=\"https://www.heyfriday.cn/\">Friday AI 智能协作系统</a>」,并且目前也做了商业化。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-1.png\" alt=\"image\" /></p>\n\n<h3 id=\"五行业内哪些人的言论值得我们日常重点关注\">五、行业内哪些人的言论值得我们日常重点关注</h3>\n\n<p>这些人的言论都值得我们关注:Sam Altman、Andrej Karpathy、Elon Musk。</p>\n\n<p>Andrej Karpathy 在其 Medium 博客上提到:</p>\n\n<blockquote>\n <p>我们都熟悉的软件 1.0 的「经典堆栈」(The classical stack)是由 Python、C++ 等语言编写的,它由程序员编写的明确的计算机指令组成。通过编写每一行代码,程序员标识了程序空间中具有某些期望行为的特定点。</p>\n</blockquote>\n\n<blockquote>\n <p>相比之下,软件 2.0 是用更抽象、不友好的人类语言(如神经网络的权重)编写的,没有人参与编写这些代码,因为权重数量很多(典型的网络可能有数百万个),并且直接用权重编写代码有一定困难(我尝试过)。</p>\n</blockquote>\n\n<p>不过打那之后 Andrej 在其博客上就再未说过一句话。</p>\n\n<p>OpenAI 创始人兼 CEO Sam Altman 曾表示:</p>\n\n<blockquote>\n <p>十年前的传统观点认为,人工智能首先会影响体力劳动,然后是认知劳动,再然后,也许有一天可以做创造性工作。现在看起来,它会以相反的顺序进行。</p>\n</blockquote>\n\n<blockquote>\n <p>通用人工智能的建成会比大多数人想象得更快,并且它会改变大多数人想象中的一切。」</p>\n</blockquote>\n\n<p>另外还有一个喜欢写博客的 AI 从业者,其博客值得我们学习与了解,就是 OpenAI 应用人工智能研究负责人 Lilian Weng,主要从事机器学习、深度学习和网络科学研究。她本科毕业于香港大学,硕士就读于北京大学信息系统与计算机科学系,之后前往印度安纳大学布鲁顿分校攻读博士。</p>\n\n<p>她的 Blog:<a href=\"https://lilianweng.github.io/\">https://lilianweng.github.io/</a>\n她的 Twitter:<a href=\"https://twitter.com/lilianweng\">https://twitter.com/lilianweng</a></p>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>https://beta.openai.com/docs/models</li>\n <li>https://karpathy.medium.com/software-2-0-a64152b37c35</li>\n <li>https://hub.baai.ac.cn/view/21726</li>\n <li>https://www.reddit.com/r/OpenAI/comments/zdrnsf/comment/iz3kfui/?context=3</li>\n <li>https://www.sohu.com/a/615541698_121255906</li>\n <li>http://blog.itpub.net/29829936/viewspace-2654536/</li>\n <li>http://tech.sina.com.cn/csj/2018-10-13/doc-ihmhafir3634167.shtml</li>\n <li>https://colab.research.google.com/github/alembics/disco-diffusion/blob/main/Disco_Diffusion.ipynb#scrollTo=DefMidasFns</li>\n <li>https://en.wikipedia.org/wiki/BERT_(language_model)</li>\n <li>https://www.mikecaptain.com/2022/12/17/ai-bert-1/</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例</title>\n \t<meta name=\"description\" content=\"2018 年 Google 发布了 BERT 模型后迅速席卷 NLP 领域,这家伙可是比 ChatGPT 背后的 GPT 还要早的。本文简单介绍了 BERT 后主要是希望大家都手试一下,所以文中提到了一个小的中文模型供大家练手,以及一个小用例。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例</h2>\t\t\n\t<time datetime=\"2022-12-17T15:08:01+00:00\" class=\"by-line\">17 Dec 2022, 杭州 | 麦克船长 | 总计 7275 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一关于-bert-的一些背景\" id=\"markdown-toc-一关于-bert-的一些背景\">一、关于 BERT 的一些背景</a></li>\n <li><a href=\"#二开始一个-bert-的动手小试验\" id=\"markdown-toc-二开始一个-bert-的动手小试验\">二、开始一个 BERT 的动手小试验</a> <ul>\n <li><a href=\"#1安装-anaconda-来为部署-bert-做环境准备\" id=\"markdown-toc-1安装-anaconda-来为部署-bert-做环境准备\">1、安装 Anaconda 来为部署 BERT 做环境准备</a></li>\n <li><a href=\"#2安装-bert-所需要的各种依赖\" id=\"markdown-toc-2安装-bert-所需要的各种依赖\">2、安装 BERT 所需要的各种依赖</a></li>\n <li><a href=\"#3下载一个预训练pre-train过的-bert-模型\" id=\"markdown-toc-3下载一个预训练pre-train过的-bert-模型\">3、下载一个预训练(Pre-Train)过的 BERT 模型</a></li>\n <li><a href=\"#5启动-bert-服务端\" id=\"markdown-toc-5启动-bert-服务端\">5、启动 BERT 服务端</a></li>\n <li><a href=\"#6在-pycharm-中使用-conda-的环境\" id=\"markdown-toc-6在-pycharm-中使用-conda-的环境\">6、在 PyCharm 中使用 Conda 的环境</a></li>\n <li><a href=\"#7编写程序实现-bert-客户端\" id=\"markdown-toc-7编写程序实现-bert-客户端\">7、编写程序实现 BERT 客户端</a></li>\n </ul>\n </li>\n <li><a href=\"#三bert-模型的优劣势及其原因\" id=\"markdown-toc-三bert-模型的优劣势及其原因\">三、BERT 模型的优劣势及其原因</a> <ul>\n <li><a href=\"#1bert-的优势是很明显的\" id=\"markdown-toc-1bert-的优势是很明显的\">1、BERT 的优势是很明显的</a> <ul>\n <li><a href=\"#11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\" id=\"markdown-toc-11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\">1.1、MLM 和 NSP 预训练能够捕捉到自然语言中的各种复杂细节</a></li>\n <li><a href=\"#12识别并专注于较重要的部分进行文本处理\" id=\"markdown-toc-12识别并专注于较重要的部分进行文本处理\">1.2、识别并专注于较重要的部分进行文本处理</a></li>\n <li><a href=\"#13快速构建针对具体任务的-nlp-系统\" id=\"markdown-toc-13快速构建针对具体任务的-nlp-系统\">1.3、快速构建针对具体任务的 NLP 系统</a></li>\n </ul>\n </li>\n <li><a href=\"#2bert-模型的劣势及其原因\" id=\"markdown-toc-2bert-模型的劣势及其原因\">2、BERT 模型的劣势及其原因</a> <ul>\n <li><a href=\"#21随机挖-mask-的完形填空题是有隐患的\" id=\"markdown-toc-21随机挖-mask-的完形填空题是有隐患的\">2.1、随机挖 MASK 的完形填空题是有隐患的</a></li>\n <li><a href=\"#22nsp-任务有必要吗\" id=\"markdown-toc-22nsp-任务有必要吗\">2.2、NSP 任务有必要吗?</a></li>\n <li><a href=\"#23针对两个或以上词组成的连续词的词义被丢失\" id=\"markdown-toc-23针对两个或以上词组成的连续词的词义被丢失\">2.3、针对两个或以上词组成的连续词的词义被丢失</a></li>\n <li><a href=\"#24需要的算力高\" id=\"markdown-toc-24需要的算力高\">2.4、需要的算力高</a></li>\n <li><a href=\"#25需要的模型大\" id=\"markdown-toc-25需要的模型大\">2.5、需要的模型大</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#四一些关于-bert-的问题\" id=\"markdown-toc-四一些关于-bert-的问题\">四、一些关于 BERT 的问题</a> <ul>\n <li><a href=\"#1bert-模型的所谓双向与-bilstm-的双向是啥区别\" id=\"markdown-toc-1bert-模型的所谓双向与-bilstm-的双向是啥区别\">1、BERT 模型的所谓「双向」与 BiLSTM 的「双向」是啥区别?</a></li>\n <li><a href=\"#2为什么-bert-可以比-rnn-更好地并行化\" id=\"markdown-toc-2为什么-bert-可以比-rnn-更好地并行化\">2、为什么 BERT 可以比 RNN 更好地并行化</a></li>\n </ul>\n </li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<h3 id=\"一关于-bert-的一些背景\">一、关于 BERT 的一些背景</h3>\n\n<p>2018 年 Google 发布 BERT 后迅速在 NLP 领域引起广泛关注。BERT(Bidirectional Encoder Representations from Transformers)是一种自然语言处理(NLP)的深度学习模型,它可以进行语言模型预测、序列标注和问答等任务。BERT 采用双向的 Transformer 编码器架构,使用了大量的数据和计算资源进行训练,因此具有较强的泛化能力。</p>\n\n<p>BERT 的训练方法是通过让模型对给定的输入文本进行自监督学习,即使用未标记的语料进行训练。BERT 可以在很多 NLP 任务中获得较好的性能,并且由于其双向的编码方式,能够更好地理解语境信息。</p>\n\n<p>BERT 的训练需要大量的计算资源,因此它常常被用来作为解决 NLP 问题的预训练模型,可以用来初始化其他模型的权重,使得这些模型能够更快速地收敛。</p>\n\n<h3 id=\"二开始一个-bert-的动手小试验\">二、开始一个 BERT 的动手小试验</h3>\n\n<p>为了让 conda 使用 Python 3.7,你可以按照这些步骤来操作。</p>\n\n<h4 id=\"1安装-anaconda-来为部署-bert-做环境准备\">1、安装 Anaconda 来为部署 BERT 做环境准备</h4>\n\n<p>先了解几个概念:Anaconda 是一个软件包管理系统,其中包含了 conda 和许多其他的工具。Conda 是 Anaconda 中的一个组件,用于安装和管理软件包。\n我们需要用 conda 创建一个环境,在这个环境里去启用我们想要使用的 BERT 所需要的各种依赖。</p>\n\n<p>更新 conda 到最新版本:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda update <span class=\"nt\">-n</span> base conda\n</code></pre></div></div>\n\n<p>使用 Python 3.7 创建一个新的环境:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda create <span class=\"nt\">-n</span> py37 <span class=\"nv\">python</span><span class=\"o\">=</span>3.7\n</code></pre></div></div>\n\n<p>激活这个新环境:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda activate py37\n</code></pre></div></div>\n\n<p>验证正在使用的是正确版本的 Python</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python <span class=\"nt\">--version</span>\n</code></pre></div></div>\n\n<p>另外你可能还会用到的 conda 命令有:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 你之后一定会需要 deactivate 一个环境,命令如下:</span>\nconda deactivate py37\n\n<span class=\"c\"># 查看 conda 当前安装的所有库</span>\nconda list\n</code></pre></div></div>\n\n<h4 id=\"2安装-bert-所需要的各种依赖\">2、安装 BERT 所需要的各种依赖</h4>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda <span class=\"nb\">install </span><span class=\"nv\">tensorflow</span><span class=\"o\">==</span>1.14.0\n</code></pre></div></div>\n\n<p>验证 tensorflow 是否安装正确:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">tensorflow</span> <span class=\"k\">as</span> <span class=\"n\">tf</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">__version__</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"3下载一个预训练pre-train过的-bert-模型\">3、下载一个预训练(Pre-Train)过的 BERT 模型</h4>\n\n<p>官方的模型在这里浏览:https://github.com/google-research/bert#pre-trained-models</p>\n\n<p>也有一些中文的模型,以下是 ChatGPT 推荐的三个:</p>\n\n<ul>\n <li>BERT-Base, Chinese:这是 Google 官方提供的中文 BERT 模型,在中文 NLP 任务中表现良好。你可以从 这里下载这个模型。</li>\n <li>ERNIE:这是由中科院自然语言所提供的中文 BERT 模型,包含了额外的语义信息。你可以从 这里下载这个模型。</li>\n <li>RoBERTa-wwm-ext:这是由清华大学自然语言处理实验室提供的中文 BERT 模型,在多种中文 NLP 任务中表现良好。你可以从 这里下载这个模型。</li>\n</ul>\n\n<p>4、安装 BERT 的服务端和客户端</p>\n\n<p>这里我们使用 bert-as-service,bert-as-service 是一种将 BERT 模型部署为服务的方式。该工具使用 TensorFlow Serving 来运行 BERT 模型,并允许通过 REST API 进行调用。根据 bert-as-service 的文档,它已经在 TensorFlow 1.14.0 上测试过。</p>\n\n<p>在你激活的环境里,安装 <code class=\"language-plaintext highlighter-rouge\">bert-as-service</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 安装服务端和客户端</span>\n<span class=\"c\"># 更多关于 bert-serving-server 的信息可以参考:https://bert-serving.readthedocs.io/en/latest/index.html</span>\nconda <span class=\"nb\">install </span>bert-serving-server bert-serving-client \n验证 bert-as-service 是否安装成功\nbert-serving-start <span class=\"nt\">-h</span>\n</code></pre></div></div>\n\n<h4 id=\"5启动-bert-服务端\">5、启动 BERT 服务端</h4>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 命令行下启动BERT服务</span>\n<span class=\"c\"># -num_worker 表示启动几个worker服务,即可以处理几个并发请求,超过这个数字的请求将会在LBS(负载均衡器)中排队等待</span>\nbert-serving-start <span class=\"nt\">-model_dir</span> /模型/的/绝对/路径 <span class=\"nt\">-num_worker</span><span class=\"o\">=</span>4\n</code></pre></div></div>\n\n<h4 id=\"6在-pycharm-中使用-conda-的环境\">6、在 PyCharm 中使用 Conda 的环境</h4>\n\n<p>在 PyCharm 中启用 Interpreter 为 Anaconda,macOS 上具体地是在「Preference - Project - Python Interpreter - Add Interpreter - Add Local Interpreter - Conda Environment」。</p>\n\n<p>接下来还有一项重要的步骤就是选择该 project 要加载包文件的路径。如果不进行这一步,那该 project 还是从系统环境变量中的路径来搜索你要加载的包,这样在你用 Anaconda 新建的这个环境中所特有的包就会出现无法加载的问题。单击菜单栏 Run 选择 Edit Configuration。在Environment variables中添加一个新的 Path。新的路径为你用 Anaconda 新建的环境的文件夹中的<code class=\"language-plaintext highlighter-rouge\">「/Users/captain/opt/anaconda3/bin/python」</code>。</p>\n\n<p>配置 PyCharm 这里参考:https://docs.anaconda.com/anaconda/user-guide/tasks/pycharm/</p>\n\n<h4 id=\"7编写程序实现-bert-客户端\">7、编写程序实现 BERT 客户端</h4>\n\n<p>这里有一些客户端例子可以参考:https://blog.csdn.net/qq_18256855/article/details/123860126</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">bert_serving.client</span> <span class=\"kn\">import</span> <span class=\"n\">BertClient</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n\n<span class=\"c1\"># 定义类\n</span><span class=\"k\">class</span> <span class=\"nc\">BertModel</span><span class=\"p\">:</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span> <span class=\"o\">=</span> <span class=\"n\">BertClient</span><span class=\"p\">(</span><span class=\"n\">ip</span><span class=\"o\">=</span><span class=\"s\">'127.0.0.1'</span><span class=\"p\">,</span> <span class=\"n\">port</span><span class=\"o\">=</span><span class=\"mi\">5555</span><span class=\"p\">,</span> <span class=\"n\">port_out</span><span class=\"o\">=</span><span class=\"mi\">5556</span><span class=\"p\">)</span> <span class=\"c1\"># 创建客户端对象\n</span> <span class=\"c1\"># 注意:可以参考API,查看其它参数的设置\n</span> <span class=\"c1\"># 127.0.0.1 表示本机IP,也可以用localhost\n</span> <span class=\"k\">except</span><span class=\"p\">:</span>\n <span class=\"k\">raise</span> <span class=\"nb\">Exception</span><span class=\"p\">(</span><span class=\"s\">\"cannot create BertClient\"</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">close_bert</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">()</span> <span class=\"c1\"># 关闭服务\n</span>\n <span class=\"k\">def</span> <span class=\"nf\">sentence_embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">):</span>\n <span class=\"s\">'''对输入文本进行embedding\n Args:\n text: str, 输入文本\n Returns:\n text_vector: float, 返回一个列表,包含text的embedding编码值\n '''</span>\n <span class=\"n\">text_vector</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span><span class=\"p\">.</span><span class=\"n\">encode</span><span class=\"p\">([</span><span class=\"n\">text</span><span class=\"p\">])[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n <span class=\"k\">return</span> <span class=\"n\">text_vector</span> <span class=\"c1\"># 获取输出结果\n</span>\n <span class=\"k\">def</span> <span class=\"nf\">caculate_similarity</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">vec_1</span><span class=\"p\">,</span> <span class=\"n\">vec_2</span><span class=\"p\">):</span>\n <span class=\"s\">'''根据两个语句的vector,计算它们的相似性\n Args:\n vec_1: float, 语句1的vector\n vec_2: float, 语句2的vector\n Returns:\n sim_value: float, 返回相似性的计算值\n '''</span>\n <span class=\"c1\"># 根据cosine的计算公式\n</span> <span class=\"n\">v1</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mat</span><span class=\"p\">(</span><span class=\"n\">vec_1</span><span class=\"p\">)</span>\n <span class=\"n\">v2</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mat</span><span class=\"p\">(</span><span class=\"n\">vec_2</span><span class=\"p\">)</span>\n <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">v1</span> <span class=\"o\">*</span> <span class=\"n\">v2</span><span class=\"p\">.</span><span class=\"n\">T</span><span class=\"p\">)</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">norm</span><span class=\"p\">(</span><span class=\"n\">v1</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">norm</span><span class=\"p\">(</span><span class=\"n\">v2</span><span class=\"p\">)</span>\n <span class=\"n\">cosine</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">/</span> <span class=\"n\">b</span>\n <span class=\"k\">return</span> <span class=\"n\">cosine</span>\n\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">\"__main__\"</span><span class=\"p\">:</span>\n <span class=\"c1\"># 创建bert对象\n</span> <span class=\"n\">bert</span> <span class=\"o\">=</span> <span class=\"n\">BertModel</span><span class=\"p\">()</span>\n <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"c1\"># --- 输入语句 ----\n</span> <span class=\"n\">input_a</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s\">'请输入语句1: '</span><span class=\"p\">)</span>\n\n <span class=\"k\">if</span> <span class=\"n\">input_a</span> <span class=\"o\">==</span> <span class=\"s\">\"N\"</span> <span class=\"ow\">or</span> <span class=\"n\">input_a</span> <span class=\"o\">==</span> <span class=\"s\">\"n\"</span><span class=\"p\">:</span>\n <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">close_bert</span><span class=\"p\">()</span> <span class=\"c1\"># 关闭服务\n</span> <span class=\"k\">break</span>\n\n <span class=\"n\">input_b</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s\">'请输入语句2: '</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># --- 对输入语句进行embedding ---\n</span>\n <span class=\"n\">a_vec</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">sentence_embedding</span><span class=\"p\">(</span><span class=\"n\">input_a</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'a_vec shape : '</span><span class=\"p\">,</span> <span class=\"n\">a_vec</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n\n <span class=\"n\">b_vec</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">sentence_embedding</span><span class=\"p\">(</span><span class=\"n\">input_b</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'b_vec shape : '</span><span class=\"p\">,</span> <span class=\"n\">b_vec</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 计算两个语句的相似性\n</span> <span class=\"n\">cos</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">caculate_similarity</span><span class=\"p\">(</span><span class=\"n\">a_vec</span><span class=\"p\">,</span> <span class=\"n\">b_vec</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'cosine value : '</span><span class=\"p\">,</span> <span class=\"n\">cos</span><span class=\"p\">)</span>\n\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'</span><span class=\"se\">\\n\\n</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 如果相似性值大于0.85,则输出相似,否则,输出不同\n</span> <span class=\"k\">if</span> <span class=\"n\">cos</span> <span class=\"o\">></span> <span class=\"mf\">0.85</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"2个语句的含义相似\"</span><span class=\"p\">)</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"不相似\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在使用 <code class=\"language-plaintext highlighter-rouge\">bert-serving-client</code> 连接 <code class=\"language-plaintext highlighter-rouge\">bert-serving-server</code> 时,你需要确保 <code class=\"language-plaintext highlighter-rouge\">bert-serving-server</code> 使用的模型和 <code class=\"language-plaintext highlighter-rouge\">bert-serving-client</code> 使用的模型是匹配的,否则会出现错误。</p>\n\n<p>程序正常运行后,将要求你输入两句话,然后 BERT 计算两句话的相似性。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>请输入语句1: \n请输入语句2: \n</code></pre></div></div>\n\n<p>两句输入好确认后,得到如下形式的结果:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>a_vec shape : (768,)\nb_vec shape : (768,)\ncosine value : 0.8691698561422959\n</code></pre></div></div>\n\n<p>其实这个小试验蛮没意思的,而且准确性也比较令人质疑。</p>\n\n<h3 id=\"三bert-模型的优劣势及其原因\">三、BERT 模型的优劣势及其原因</h3>\n\n<p>论文地址:<a href=\"https://arxiv.org/abs/1810.04805\">《BERT: Pre-Training of Deep Bidirectional Transformers for Language Understanding》</a> 。</p>\n\n<h4 id=\"1bert-的优势是很明显的\">1、BERT 的优势是很明显的</h4>\n\n<p>复旦大学的邱锡鹏教授层评价 BERT 的「里程碑意义」在于:</p>\n\n<blockquote>\n <p>证明了一个非常深的模型可以显著提高 NLP 任务的准确率,而这个模型可以从无标记数据集中预训练得到。</p>\n</blockquote>\n\n<h5 id=\"11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\">1.1、MLM 和 NSP 预训练能够捕捉到自然语言中的各种复杂细节</h5>\n\n<p>因为 BERT 采用了双向的自注意力机制,这里的「双向」意味着 BERT 模型可以同时利用输入文本的前后文信息来预测下一个词是什么、下一句是什么。这样 BERT 模型就可以捕捉到自然语言中的各种隐藏的细节,比如语义关系、语法结构、语义暗示等等。</p>\n\n<p>具体地,BERT 采用了 Masked Language Model(MLM)来做「下一个词是什么」的预训练,采用了 Next Sentence Prediction(NSP)来做「下一句是什么」的预训练。MLM 的方式其实就很像英语考试里的「完形填空」,而 NSP 的方式,就像整句的完形填空。</p>\n\n<h5 id=\"12识别并专注于较重要的部分进行文本处理\">1.2、识别并专注于较重要的部分进行文本处理</h5>\n\n<p>这要得益于因为 BERT 采用了自注意力机制。自注意力机制,通过计算输入单元的权重值,来确定在一个输入序列中哪些输入单元是重要的。具体地,一个输入单元与其他单元的相似性越高,按照我们自然语言的逻辑,那么这部分是在被重复、强调、翻来覆去用不同的方式在解释,那么这部分就是重要的,权重值就更高。</p>\n\n<h5 id=\"13快速构建针对具体任务的-nlp-系统\">1.3、快速构建针对具体任务的 NLP 系统</h5>\n\n<p>因为 BERT 采用了预训练模型,能够在没有监督标注数据的情况下从大量文本中学习语言模型。因为我们认为上下文信息本身就能推测出某个词,所以大量的文本数据本身就是一种「自带标注」的数据,所以 BERT 能够无监督学习。</p>\n\n<h4 id=\"2bert-模型的劣势及其原因\">2、BERT 模型的劣势及其原因</h4>\n\n<h5 id=\"21随机挖-mask-的完形填空题是有隐患的\">2.1、随机挖 MASK 的完形填空题是有隐患的</h5>\n\n<p>对于上面提到的 MLM、NSP 方法做预训练,那么问题也就显而易见了,如果我们挖掉的一组 MASK 完形填空词,是强关联的(非条件独立),那么这一组词的预测就都会出现问题。</p>\n\n<h5 id=\"22nsp-任务有必要吗\">2.2、NSP 任务有必要吗?</h5>\n\n<p>论文《Crosslingual language model pretraining》中提到 BERT 的 NSP 可能是非必要的,针对这个问题,后续出现的模型都移除了 NSP 任务,比如 RoBERTa、spanBERT、ALBERT。</p>\n\n<h5 id=\"23针对两个或以上词组成的连续词的词义被丢失\">2.3、针对两个或以上词组成的连续词的词义被丢失</h5>\n\n<p>比如 cutting-edge,MLM 的方式可能会割裂这两个子词的相关性,导致模型丢失这个词的词义,针对这个问题 Google 后来发表了 BERT-WWM,WWM 即 Whole Word Masking,从字面就能理解针对的问题。哈尔滨工业大学的科大讯飞联合实验室后来推出了 Chinese-BERT-WWM 专门针对中文解决了这个问题。</p>\n\n<h5 id=\"24需要的算力高\">2.4、需要的算力高</h5>\n\n<p>算力高,自然需要的计算成本运行更高。不过算力成本高这种问题总有办法优化,通常来说不是模型本身所处理问题的局限性和先决条件的局限性(比如依赖大量人工工作)就非常好了。</p>\n\n<h5 id=\"25需要的模型大\">2.5、需要的模型大</h5>\n\n<p>模型大,自然存储成本也就高了。这也类似于上一点,而且算力、存储成本高,可以在大型应用中把成本均摊下来,比如 BERT 如果支持的某个 AGI 应用得到广泛普及。</p>\n\n<h3 id=\"四一些关于-bert-的问题\">四、一些关于 BERT 的问题</h3>\n\n<h4 id=\"1bert-模型的所谓双向与-bilstm-的双向是啥区别\">1、BERT 模型的所谓「双向」与 BiLSTM 的「双向」是啥区别?</h4>\n\n<p>BiLSTM 是把句子再倒序一遍,而 BERT 的双向是指在 Encoder 的自注意力机制下编码一个 token 时「同时利用上下文」的 token。</p>\n\n<h4 id=\"2为什么-bert-可以比-rnn-更好地并行化\">2、为什么 BERT 可以比 RNN 更好地并行化</h4>\n\n<p>RNN 因为有时序概念,即后面的特征计算,依赖于前面计算的结果,所以就形成了循环(Recurrent)。而 BERT 采用了自注意力机制则没有时序概念,每个词特征都依赖其上下文独立计算,因此更容易并行化。</p>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>https://arxiv.org/abs/1810.04805</li>\n <li>https://github.com/google-research/bert</li>\n <li>https://github.com/ymcui/Chinese-BERT-wwm</li>\n <li>https://zhuanlan.zhihu.com/p/195723105</li>\n <li>https://www.jiqizhixin.com/articles/2018-10-24-13</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</title>\n \t<meta name=\"description\" content=\"最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是「Optimizing Language Models for Dialogue」,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</h2>\t\t\n\t<time datetime=\"2022-12-11T15:59:57+00:00\" class=\"by-line\">11 Dec 2022, 杭州 | 麦克船长 | 总计 1692 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-12-11-wechat-chatgpt-3.png\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n<p>最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是 <strong>「Optimizing Language Models for Dialogue」</strong>,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。</p>\n\n<h3 id=\"stepbystep\">Step by step</h3>\n\n<p>本实践依赖:CLI、Docker、npm、Github、fuergaosi233/wechat-chatgpt、git、YAML、Chrome 的使用。以下将简洁地 Step by step 列出步骤。</p>\n\n<p>第一步,你要现有一个 OpenAI 的账号,注意注册时手机号不能是中国大陆或香港的,IP 地址和 GPS 也不能暴露你是中国大陆或者香港的。</p>\n\n<p>第二步,准备一台服务器(否则个人电脑要一直处于开机运行状态),由于后面将用到 Session Token 来登录,因此 IP 地址是香港也没关系,于是我是在我的香港服务器上部署 wechat-chatgpt</p>\n\n<p>第三步,在服务器上安装 Docker,不赘述。</p>\n\n<p>第四步,从 Github 上拉取项目项目到服务器上。</p>\n\n<p>第五步,任何设备上登录 ChatGPT,用 Chrome 的 Inspect 来查看并复制 session token 到剪贴板。</p>\n\n<p>第六步,编辑 wechat-chatgpt 的 config.yaml,填写 session token;设置 private trigger keywords(可选)。</p>\n\n<div class=\"language-yaml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"na\">chatGPTAccountPool</span><span class=\"pi\">:</span>\n <span class=\"pi\">-</span> <span class=\"na\">email</span><span class=\"pi\">:</span> <span class=\"s\"><your email></span>\n <span class=\"na\">password</span><span class=\"pi\">:</span> <span class=\"s\"><your password></span>\n<span class=\"c1\"># if you hope only some keywords can trigger chatgpt on private chat, you can set it like this:</span>\n<span class=\"na\">chatPrivateTiggerKeyword</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">\"</span>\n</code></pre></div></div>\n\n<p>第七步,用 docker 来拉取 wechat-chatgpt</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker pull holegots/wechat-chatgpt:latest。\n</code></pre></div></div>\n\n<p>第八步,启动 wechat-chatgpt:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker run <span class=\"nt\">-d</span> <span class=\"nt\">--name</span> wechat-chatgpt <span class=\"nt\">-v</span> <span class=\"si\">$(</span><span class=\"nb\">pwd</span><span class=\"si\">)</span>/config.yaml:/app/config.yaml holegots/wechat-chatgpt:latest\n</code></pre></div></div>\n\n<p>注意,如果手动模式下也可以用npm run dev启动。如果提示系统不认识 npm 则可以运行 <code class=\"language-plaintext highlighter-rouge\">npm install && poetry install</code> 来解决。到此你就可以在微信上跟这个打通了 ChatGPT 的账号聊天了。</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-1.png\" alt=\"image\" style=\"width:100%\" /></th>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-2.png\" alt=\"image\" style=\"width:100%\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>其实可以看到这个 AI 船长不管是专业性问题(计算机相关)还是非专业问题,都回答的很不错。</p>\n\n<p>如何停止、重启、查看日志呢?首先停止的命令是docker stop wechat-chatgpt,登录时需要扫码登录微信并追踪 logs,因为这其实是用了微信在桌面端的接口。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker logs <span class=\"nt\">-f</span> wechat-chatgpt\n</code></pre></div></div>\n\n<p>会在 Terminal 里显示一个文字阵列组成的桌面端微信登录二维码,用你打算做成微信 AI 机器人那个微信号扫一下,相关信息都填完。另外,这样最好别用自己的微信大号,而是用一个小号。微信不让聊这些,小号注意要完成实名认证。</p>\n\n<p>如果要停止运行,用如下命令:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker stop wechat-chatgpt\n</code></pre></div></div>\n\n<h3 id=\"参考\">参考</h3>\n\n<p>1、<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/tree/main\">https://github.com/fuergaosi233/wechat-chatgpt/tree/main</a></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>不要船开远了,就忘了为什么启航</title>\n \t<meta name=\"description\" content=\"2020 年的 6 月 4 日我入职阿里巴巴集团,7 天后的 6 月 11 日我写下了这篇文章。偶然翻到了当时这篇文章,遂转录于此,提醒自己勿忘初心。在不涉及到公司数据安全及商业机密问题的前提下,稍做了一些删改,发布在这里作为一个回顾。本次穿插了一些图片,当时写的时候还没有这些照片。本文内容包括:很多人是带着梦想来阿里的,那么我的梦想是什么呢?最喜欢新六脉的哪句话?为什么?关于阿里企业价值观:为什么要接受这套价值观?价值观的本质意义(极度务实视角)是什么?Landing 的 SOP;问问自己,来到阿里,如果初期我可能需要做一点改变,那会是什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>不要船开远了,就忘了为什么启航</h2>\t\t\n\t<time datetime=\"2022-08-11T15:53:57+00:00\" class=\"by-line\">11 Aug 2022, 杭州 | 麦克船长 | 总计 3223 字</time>\n\t<div class=\"content\">\n\t\t<h3 id=\"写在前面\">写在前面</h3>\n<p>偶然翻到 2020.06.11 刚来到阿里时写的一篇内容(我是 2020 年的 6 月 4 日我入职阿里巴巴集团),是有关于来阿里的期待、对这家公司的一些粗浅初步的理解。此时再翻来看看,最大的感触就是,提醒自己勿忘初心。</p>\n\n<p>在不涉及到公司数据安全及商业机密问题的前提下,稍做了一些删改,发布在这里作为一个回顾。本次穿插了一些图片,当时写的时候还没有这些照片。本文内容包括:</p>\n\n<ul>\n <li>很多人是带着梦想来阿里的,那么我的梦想是什么呢?</li>\n <li>最喜欢新六脉的哪句话?为什么?</li>\n <li>关于阿里企业价值观:为什么要接受这套价值观?</li>\n <li>价值观的本质意义(极度务实视角)是什么?</li>\n <li>Landing 的 SOP</li>\n <li>问问自己,来到阿里,如果初期我可能需要做一点改变,那会是什么?</li>\n</ul>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-1.png\" alt=\"image\" /></p>\n\n<p>注:2020 年平安夜 · 百年湖畔 87 期合影</p>\n\n<h3 id=\"很多人是带着梦想来阿里的那么我的梦想是什么呢\">很多人是带着梦想来阿里的,那么我的梦想是什么呢?</h3>\n\n<p>Christensen 在《创新者的窘境》中提到:每一次技术更迭,都需要破坏性创新,而破坏性创新在前一次技术更迭的胜出者内部是很难生长出来的。阿里诞生以来,不断地创造第二增长曲线:阿里巴巴、淘宝、支付宝、天猫、阿里云、钉钉 …… 这让我非常好奇。其中很多产品穿越多个时间周期,期间不断创造内生二次曲线。</p>\n\n<p>但是阿里也一样错失了很多,微信、美团、拼多多、抖/快…… 等等很多产品诞生在了其他公司,还有某些产品在不断的科技更迭中自身生长出了第二曲线。</p>\n\n<p>因此我来阿里的梦想也非常明确:<strong>参与或创造一次(甚至多次)第二曲线,可以是新产品,也可以是原有产品内生的。在这个过程中获得个人成长、个人价值。</strong></p>\n\n<p>一直以来,我有三个最想实现或得到的东西:LOVE、CREATION、FREEDOM。随着生活与工作的前行,对这三者的理解,在不断加深。在这个问题里,我想应该是讨论”CREATION”。</p>\n\n<p>CREATION 上,我的梦想的范式,大概是从自己中学时代就确立了,在某一次人类社会变革浪潮中,扮演有一定权重的角色。这里面有几个变量:<strong>什么领域(F)的变革;什么规模(S)的变革;多大的权重(W);什么角色(R)。</strong></p>\n\n<p>F 这个变量,我在中学及大学时代逐渐明确,是以相对普适的产品形式输出结果并对社会变革产生积极作用。后来越来越明确为科技与商业结合的领域。</p>\n\n<p>S、W 这两个变量,自然是越大越好。因此我会希望能够构建尽可能大的机会,或者参与到尽可能大的机会中。R 希望是有强烈 Ownership 的身份。</p>\n\n<p>因此过去几年我选择了创业。创业就像冲浪,你抓住一次浪并完成漂亮的动作,就是一次不算失败的创业。但是如果一个浪没抓住,你去追它是没意义的,而应该等待下一个浪。我认为在未来 5~10 年内难以出现规模能大到令我足够兴奋的科技浪潮。大浪潮中属于创业者的大机会很多,而中小浪潮的大机会基本只属于大平台,那么为了在壮年期做获得我的 CREATION,我选择了加入阿里这样的大平台。</p>\n\n<p>在最后做决定以及初来阿里的那个人生转折点,作为老阿里人的曲洋老师对我说的一句话,深深地鼓励了我,他说:”带着创业气质,把这里当你的舞台折腾!”</p>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-2.png\" alt=\"image\" /></p>\n\n<p>注:2020 年双十一 · 淘宝 KO</p>\n\n<h3 id=\"最喜欢新六脉的哪句话为什么\">最喜欢新六脉的哪句话?为什么?</h3>\n\n<p>最喜欢的是“因为信任所以简单”。</p>\n\n<p>我一直认为人最重要的两个元特质是”真实”和“谦逊”,由”真实”可以塑造自我(对内)、构建信任(对外),后者可以带来清晰的边界,继而实现人与人之间高效的互动(这种互动包括各种人际关系在内,如婚姻、合伙、共事、合作等等)。</p>\n\n<p><strong>事物(虚实皆可)呈现在人的认知中,会得到三方面的投影:facts、opinion、feeling。如果我们足够真实,当我们需要把这三方面呈现给他人时,双方就能顺畅建立信任。信任的结果,就对应到这三方面:彼此之间建立共识(facts)、求同存异(opinion)、尊重感受(feeling),这就是”简单”。</strong></p>\n\n<p>另外一句是鼓励自己勇于绽放的一条:「此次此刻,非我莫属」。</p>\n\n<p>激情、自信、积极…… 通常行为统一表现为“勇于绽放自己”,绽放有表达(语言)与投身(行为)两种表现形式。更进一步推进就是”此次此刻,非我莫属”的阿里价值观。</p>\n\n<p>低调、稳重、谦逊,其实与“此次此刻,非我莫属“,并不矛盾。这点是我来到阿里后,发现自己在过去这些年的创业中已经不知不觉改变了,从 Introvert 逐渐变成了 Extrovert 的人,而且从曾经 social 中消耗能量,逐渐变为我现在可以感知到获得能量。这种变化,是我最近来阿里才确认发生的,此前因为自己创业者的身份没有察觉这种变化的发生。</p>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-3.png\" alt=\"image\" /></p>\n\n<p>注:2021 年秋 · 径山之行</p>\n\n<h3 id=\"关于阿里企业价值观为什么要接受这套价值观\">关于阿里企业价值观:为什么要接受这套价值观?</h3>\n\n<p>马老师和老逍都提到这个:我们是寻找同路人,而不是教育别人。这其实非常明晰地解释了为什么阿里要构建一个毛细血管网络一样的政委体系。基于这种用人理念,政委体系不敢说是最优解,但一定是优解(而且是否有更优解的论证没有意义)。</p>\n\n<p>对于个人,我的理解是要做两件事:<strong>1)构建自己的价值观体系(初始化);2)寻找价值观契合的公司(做匹配)。这两点里,没有任何地方提到”你要改变价值观为了契合你所在的公司”。</strong></p>\n\n<p>而企业价值观呢,其实可以分两部分看待:普世价值观、独特价值观。前者因为是普世的,所以到了哪个公司这种价值观都对,这点老逍也提了,比如“客户第一”。后者是个性化的,但不存在孰高孰低,就像一个人内向还是外向,你不能说哪个是错的。</p>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-4.png\" alt=\"image\" /></p>\n\n<p>注:2021 年双十一 · 天天特卖团队</p>\n\n<h3 id=\"价值观的本质意义极度务实视角是什么\">价值观的本质意义(极度务实视角)是什么?</h3>\n\n<p><strong>在充分考虑价值观适配使命、愿景基础上,价值观本身的意义,在和风细雨时(即企业价值观与其他价值判断相 match 时),是看不到的。但在暴风骤雨时(即企业价值观与其他价值判断相冲突时),就能显示其实实在在的作用了。</strong>我认为包括三类,前两个是阿里整体视角,第三个是阿里内部:</p>\n\n<ul>\n <li>经济体内,阿里与其他生态位的冲突或损益关系,如曾经的美蘑口一役。</li>\n <li>经济体内,其他的生态位之间的冲突或损益关系,如曾经的十月围城。</li>\n <li>阿里人的行为价值判断,如最近的钉钉代考事件、过往的各类廉政事件。</li>\n</ul>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-5.png\" alt=\"image\" /></p>\n\n<p>注:2021 年冬 · 淘宝天猫合并前合影</p>\n\n<h3 id=\"landing的sop\">Landing 的 SOP</h3>\n\n<p>大家都说 landing 充满挑战,马老师其实给出了 landing 的 SOP 三部曲:<strong>一起打过仗、一起创过新、一起度过难。三个经历都 close 才算 smooth landing。</strong></p>\n\n<p>集团人才策略层面、HR 实操层面、Leader 层面、,对于新人 landing 能做到什么程度的保障,其实每个新人感受到的不尽相同:</p>\n\n<ul>\n <li>集团层面,始终是在构建更好的新人 landing 环境的,这符合自身价值,这能打下很好的底子。</li>\n <li>实操层面,包括面试阶段对候选人的价值观判断、预期管理,面试及入职后公司文化及人才体系的事实呈现、内化吸收和长期解惑。</li>\n <li>Leader 层面,这是新人体感最强烈的部分,也是最重要的部分。尽管拥抱变化,但首先 Leader 需要给出尽可能最全面的考虑,其次是对候选人的预期管理。好的 Leader 会给候选人提供合理的着陆点、多个降落伞、缓冲垫,完成 smooth landing。</li>\n</ul>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-6.png\" alt=\"image\" /></p>\n\n<p>注:2021 年夏 · 出差厦漳泉</p>\n\n<h3 id=\"问问自己来到阿里如果初期我可能需要做一点改变那会是什么\">问问自己,来到阿里,如果初期我可能需要做一点改变,那会是什么?</h3>\n\n<p>曾经个人的激情与动力,常来自于“增长”。常说<strong>高增长掩盖一切</strong>,所以未来在阿里如果不能如创业般快速获得反馈得到积极结果,并且大平台中必然要接受大量关联方共同参与项目而导致的效率降低,因此我要逐渐改变自己,重新适应这种环境下的激情与动力获得方式。</p>\n\n<p>另一方面,作为创业公司的负责人,工作中鲜有因为内部原因而无法推进的事情,但是扮演肩部或腰部角色时,需要接受头部决策的一定程度不可控,这是我需要作出的适应与改变。关于这一点,我在几个月前就已经在做预期管理和心态调整,我认为以创业者的强适应性,这可能并不会是问题,但是我习惯于保持谨慎的乐观来面对自己。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>又是一年 Birthday!</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>又是一年 Birthday!</h2>\t\t\n\t<time datetime=\"2022-07-29T15:53:57+00:00\" class=\"by-line\">29 Jul 2022, 青岛 | 麦克船长 | 总计 0 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-07-27-captain-birthday-1.png\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>欢迎成为「淘宝-天天特卖」团队的创业合伙人!</title>\n \t<meta name=\"description\" content=\"阿里内部创业项目「天天特卖」招合伙人啦!以「特卖合伙人」为基石的、以「使众人行」的战友感为人才基本要求、以「用人做事,而非做事用人」为人才建设核心,是天天特卖团队的组织管理理念。天天特卖期待你的加入!\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>欢迎成为「淘宝-天天特卖」团队的创业合伙人!</h2>\t\t\n\t<time datetime=\"2021-11-11T19:59:43+00:00\" class=\"by-line\">11 Nov 2021, 杭州 | 麦克船长 | 总计 917 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2021-11-11-captain-tttm-1.jpg\" alt=\"imagee\" /></p>\n\n<h3 id=\"天天特卖团队理念\">天天特卖团队理念</h3>\n\n<h4 id=\"特卖合伙人\">特卖合伙人</h4>\n\n<p>以「特卖合伙人」为基石的、以「使众人行」的战友感为人才基本要求、以「用人做事,而非做事用人」为人才建设核心,是天天特卖团队的组织管理理念。特卖核心管理团队每 Q 会进行一次班子建设通晒。</p>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-9.jpg\" alt=\"imagee\" /></p>\n\n<h4 id=\"如何理解协作\">如何理解协作?</h4>\n\n<p>从长时间线来看,我们是为了不断积累信用,像一张信用卡一样,不断获得别人愿意支持我们的更大额度。不要事情结果还可以,而我们却没有积累到信用。互联网本质也是现代工业。而现代工业,一是社会分工,二是社会协作。想取得现代工业项目的结果,就要有更大的人才包容度、环境包容度。工作的结果就是在妥协与博弈中取得的,这是和光同尘的本质,也是现代工业复杂系统拿到结果的本质。只有这样我们才能让越来越多的人追随我们一起 do something,这种追随不一定只有上下级才是,而是愿意并且相信和我们能到达更远的地方。这背后的信用,要我们一步一个脚印地去积累,对他人给予的信任要保持敬畏、如履薄冰、懂得感恩。对每一段阶段性或长或短结束的合作,都要表达感谢。</p>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-8.jpg\" alt=\"imagee\" /></p>\n\n<h4 id=\"如何看待同学的优势及短板\">如何看待同学的优势及短板?</h4>\n\n<ul>\n <li>优势:讲优势有两个可能的目的,要么组织会在未来任务分配上重点考虑发挥该同学优势的事情,要告诉 TA,要激励 TA,是 TA 前行的自信来源之一。要么是对于同学也把握不准的特点,我们明确告诉 TA 这是你被我欣赏的优点。</li>\n <li>短板:什么是要讲的短板?未来一段时间,最期待你补足提升的。一旦这方面显著进步,就会向上迈进很大一步,甚至可以突破自己当下成长的瓶颈。要花多少篇幅讲?要比优势,有更大篇幅去讲。讲完就结束了么?对这个短板,一定要表达态度,也一定要对是否有方法、什么方法来补足短板要和同学沟通。</li>\n <li>无论是优势,还是短板,要说到点儿上,不要说片儿汤话。要让同学们能够引起思考、启发的。</li>\n</ul>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-10.jpg\" alt=\"imagee\" /></p>\n\n<h3 id=\"天天特卖期待你的加入\">天天特卖期待你的加入!</h3>\n\n<p>新天天特卖缘起于「手淘下沉市场战役)」,于 2021 年初上线,以「极致性价比货源、裸价直降、全网比价、买贵必赔」打造手淘极致价格敏感人群的购物阵地。目前天天特卖团队有行业运营、用户运营、数据策略、整合营销、直播运营、内容运营等岗位,有兴趣的同学可以钉钉随时找我,期待你的加入!</p>\n\n<h4 id=\"欢迎添加我的微信sinosuperman-推荐自荐--\">欢迎添加我的微信:sinosuperman 推荐、自荐 ^ ^</h4>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-11.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-2.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-3.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-4.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-5.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-6.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-7.jpg\" alt=\"imagee\" /></p>\n\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的阿里一年香(入职阿里一周年)</title>\n \t<meta name=\"description\" content=\"本文记录了麦克船长来到阿里巴巴集团整整一年时,麦克船长的主管给的寄语。考虑到公司商业敏感问题,做了一定的删节。现记录于此,用于以后的回顾。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的阿里一年香(入职阿里一周年)</h2>\t\t\n\t<time datetime=\"2021-06-04T15:42:43+00:00\" class=\"by-line\">04 Jun 2021, 杭州 | 麦克船长 | 总计 306 字</time>\n\t<div class=\"content\">\n\t\t<p>To 麦克船长</p>\n\n<p>1 周年快乐!很开心我们有这样一段共事的机会,虽开始时有些许波折,但随着进一步相处,我们很快能做到彼此欣赏、英雄相惜、默契配合,也特别感谢你对我的信任和支持,这是一切共事的基础。你强大的自驱力、脑力、对新事物的理解学习能力,都是最近几手新人里比较突出的。特别钦佩于你的执着和初性,对一件事认定后,迸发出的强大战斗力和决心。今天特卖这个新业务需要扎下根基,还真的需要一些舍我其谁的胆魄和更为犀利的突破,我也相信「新特卖」能成为你在阿里又一代表作,我希望我们的团队能为之骄傲和自豪,我们能不负公司所托,真正在下沉市场这场硬仗上有所建树,井取得令我们自己感到骄傲的突破,一起加油。</p>\n\n<p>From 麦克船长的主管</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>担任淘宝产品总负责人的双十一,是怎样的体验?</title>\n \t<meta name=\"description\" content=\"本文记录了一些影像,是关于麦克船长来到阿里巴巴集团的第一个双十一,负责担任淘宝的总PD(产品总负责人)。一年一度的双十一成了淘宝,乃至整个阿里集团的传统,就像阿里这家公司的春节过年一样,气氛热烈,而且消费者和商家朋友们也都会跟我们一同迎来一次购物与销售的狂欢。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>担任淘宝产品总负责人的双十一,是怎样的体验?</h2>\t\t\n\t<time datetime=\"2020-11-11T15:59:43+00:00\" class=\"by-line\">11 Nov 2020, 杭州 | 麦克船长 | 总计 138 字</time>\n\t<div class=\"content\">\n\t\t<p>说是体验,其实本文只记录了一些影像,是关于麦克船长来到阿里巴巴集团的第一个双十一,负责担任淘宝的总PD(产品总负责人)。一年一度的双十一成了淘宝,乃至整个阿里集团的传统,就像阿里这家公司的春节过年一样,气氛热烈,而且消费者和商家朋友们也都会跟我们一同迎来一次购物与销售的狂欢。</p>\n\n<p><img src=\"/img/src/2020-11-11-captain-double-eleven-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-2.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-3.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-4.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-5.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-6.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-7.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-8.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-9.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-10.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-11.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>疫后怎么做餐饮品牌?三叉戟模式或成标配</title>\n \t<meta name=\"description\" content=\"2020 新型冠状病毒疫情,给所有商业领域都带来了巨大影响,而餐饮业可以说是首当其冲,但这同时也带来了很多多元化经营的启示。我们回归原点,餐饮业解决了我们什么需求?吃饭。但是当我们不选择去饭店就餐时,我们如何解决吃饭问题?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>疫后怎么做餐饮品牌?三叉戟模式或成标配</h2>\t\t\n\t<time datetime=\"2020-04-14T16:42:43+00:00\" class=\"by-line\">14 Apr 2020, 杭州 | 麦克船长 | 总计 1845 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<p><img src=\"/img/src/2020-04-15-covid2019-catering-business-mode-1.jpg\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n\n<p>2020 新型冠状病毒疫情,给所有商业领域都带来了巨大影响,而餐饮业可以说是首当其冲,但这同时也带来了很多多元化经营的启示。</p>\n\n<p>我们回归原点,餐饮业解决了我们什么需求?吃饭。但是当我们不选择去饭店就餐时,我们如何解决吃饭问题?</p>\n\n<ul>\n <li>外卖</li>\n <li>做饭</li>\n <li>速食</li>\n</ul>\n\n<p>而这三方面,恰恰就是餐饮企业多元化经营的答案。</p>\n\n<h3 id=\"标品化外卖业务\">标品化外卖业务</h3>\n\n<p>一些以堂食为主的大餐饮品牌,比如海底捞、太二酸菜鱼、呷哺呷哺、西贝莜面村等等,应该更加重视外卖的价值了。重视到什么程度?比如这次疫情的影响,让你的成本与收入结构决定你只能关店,那说明你的外卖业务体量仍太小,过度依赖于堂食营收。</p>\n\n<p>其实大品牌做外卖,具有先天优势:信任度、定价优势。</p>\n\n<p>将门店部分菜品做标准化,设定外卖菜单,将外卖业务作为门店的重要多元化经营手段之一:</p>\n\n<ul>\n <li>形成<strong>场景互补</strong>,可以<strong>增强抗风险能力</strong>,除了这次的瘟疫,其他很多情况都会导致消费者外储减少,进而出现区域性的门店营收下降,比如台风、雾霾、暴雨等等。</li>\n <li><strong>增加品牌露出渠道</strong>。门店模式,以线下地段的人流曝光、点评等「到店」为主的互联网平台曝光为主,而外卖可以带来「到家」为主的互联网平台曝光。</li>\n</ul>\n\n<h3 id=\"社区生鲜前置仓\">社区生鲜前置仓</h3>\n\n<p>数据显示,京东生鲜配送到家业务相对节前环比增长 370%,叮咚买菜大年三十的订单量同比上月增长超过 300%;美团买菜在北京地区的日订单量达到了春节节前单量的 2-3 倍;除夕至初四,每日优鲜平台实收交易额较去年同期增长 321%。</p>\n\n<p>而餐饮门店,先天性地就需要大量采购生鲜食材、调味品,而采购量如果不合理,还会出现库存积压甚至损失的问题。如果餐饮连锁品牌把每家门店本身,变为一个生鲜食材的社区前置仓,反而比生鲜电商更具有优势。</p>\n\n<p>从另一个角度说,叮咚买菜、美菜等生鲜电商平台,甚至美团,也可以寻求和某个或某几个门店数量较多、分布较匹配的餐饮连锁品牌合作,对于自己的市场扩张也是很大的助力,是一种双赢。</p>\n\n<p>这样看来,盒马鲜生最初尝试的「超市+堂食+生鲜配送」的模式,或成为最佳先行者案例。以购物为主业的物美、永辉、联华,其实都可以进化成这种模式,而以堂食为主业的餐饮门店,可以用更社区的方式,进化成这种模式。</p>\n\n<p>在这方面,餐饮企业应该发挥自身优势,避开短板。购物业态发展起来的生鲜配送,往往只能提供蔬菜禽蛋肉,而餐饮企业除此之外,还可以提供半成品食材,方便消费者进行简餐烹饪就可做出一道菜。</p>\n\n<p>总结下餐饮做社区生鲜前置仓的特点:</p>\n\n<ul>\n <li><strong>场景互补,增加收入模式,提升抗风险能力</strong></li>\n <li>培养消费者习惯,<strong>加深品牌认知</strong></li>\n <li><strong>加速库存周转</strong>,提升采购弹性</li>\n</ul>\n\n<h3 id=\"线上预包装食品\">线上预包装食品</h3>\n\n<p>从本次疫情的速食类预包装食品销售大幅增长来看,当人们无法外出就餐,也不想自己生火做饭时,速食预包装食品依然是最重要的就餐保底选择。</p>\n\n<p>大餐饮品牌非常适合拓展预包装食品,而且消费者认知里会觉得大餐饮品牌的预包装食品更有品质、更安全。这样就需要品牌选好关联品类,比如川菜、湘菜品牌,推出辣味食品就很符合消费者心智认知;新疆、内蒙的地方特色餐饮品牌,则可以提供牛羊肉类的预包装零食;海鲜类餐饮品牌,可以推出水产类零食,等等。</p>\n\n<p>提供预包装食品,会从四方面助力餐饮品牌发展:</p>\n\n<ul>\n <li><strong>场景互补,增加收入模式,提升抗风险能力</strong></li>\n <li><strong>更多渠道触达</strong>,原来传统餐饮品牌,在互联网领域最多触达到到店消费和外卖两个场景。增加预包装食品后,可以在众多电商平台曝光,并且进一步的增加抖音、快手、小红书、淘宝直播等自媒体种草与带货渠道,还可以在有赞、微盟支持先与公众号流量主合作。更进一步的还有社交电商、微商体系。</li>\n <li><strong>突破门店区域触达限制</strong>,对于预包装食品,只要快递能到达的范围,都是自己的客户覆盖区域。</li>\n <li>加深消费者认知,可以在一日三餐之外,有更多的场景唤起消费者。</li>\n</ul>\n\n<h3 id=\"线下重构新餐饮时代到来\">线下重构,新餐饮时代到来</h3>\n\n<p>危机也是发展的契机,这一次疫情必然带来线下全面的线下格局洗牌。</p>\n\n<ul>\n <li>用「标品化外卖」覆盖外卖场景</li>\n <li>用「生鲜前置仓」覆盖做饭场景</li>\n <li>用「预包装食品」覆盖速食场景</li>\n</ul>\n\n<p>而餐厅核心能力,为这三方面做供给支撑,这就是我们说的「<strong>三叉戟模式</strong>」。受翻台率限制的堂食则作为一个可选项,对客单价偏高的餐饮品牌,堂食依然占据重要意义,而低客单价的餐饮品牌或许三只尖刺的杀伤力会远超主柄。</p>\n\n<p>对于餐饮品牌来说,未来运用三叉戟模式,可能会成为一种常态。率先做出这种布局调整的餐饮品牌,会在线下流量重构的过程中,成为新餐饮时代的代表。</p>\n\n<p>最后还是想说一句,希望疫情早些结束,希望中国的餐饮企业们沉着应对,降成本、转模式都要趁早。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>延迟满足,才有自由</title>\n \t<meta name=\"description\" content=\"今天我们来聊聊延迟满足(Delayed Gratification)和即时满足(Instant Gratification)。面对不同的「对手」,我们要做到不同深度的延迟满足。而延迟满足的驻留时间,则量化了我们在相应深度上的延迟满足能力。有意培养,刻意练习,用延迟满足来帮助自我成长,是一个长期课题,我也在路上。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>延迟满足,才有自由</h2>\t\t\n\t<time datetime=\"2020-04-11T06:18:03+00:00\" class=\"by-line\">11 Apr 2020, 杭州 | 麦克船长 | 总计 4478 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<h3 id=\"写在前面\">写在前面</h3>\n\n<p>今天我们来聊聊延迟满足(Delayed Gratification)和即时满足(Instant Gratification)。</p>\n\n<h3 id=\"1儿童时期的延迟满足\">1、儿童时期的延迟满足</h3>\n\n<h4 id=\"棉花糖实验\">棉花糖实验</h4>\n\n<p>聊到延迟满足,就必须提到沃尔特·米歇尔教授(Walter Mischel)的「棉花糖实验」。在 1960 年时, 米歇尔教授对美国斯坦福大学宾恩幼儿园 600 名 4~6 岁的儿童进行了测试:小朋友们可以选择桌上的棉花糖、奥利奥饼干或椒盐脆饼,然后可以立即吃掉,或者等一会儿再吃。如果等一会儿的话,会得到奖励。</p>\n\n<p>研究发现不同的小朋友有不同的表现,平均延迟时间为 15~20 分钟,有些小朋友延迟时间很短,有的则比较长。而 20 多年以后米歇尔教授追踪实验当年的小朋友们,发现延迟时间与学业成功存在正相关性。</p>\n\n<p>而今天我想更多地从我自身的感受来看,在不同方面的延迟满足或即时满足,可能是幼儿时期就有的,也可以是成年后习得或改变的。但无论何时,我们都应该对此报以重视。</p>\n\n<p>先讲两个我自己的例子吧。</p>\n\n<h4 id=\"黑加仑零食\">黑加仑零食</h4>\n\n<p>我有一个哥哥。在我还没有上学的时候,父母每隔一段时间会给我们购买很多零食,买什么也是我们自己决定的。回到家里,我和哥哥会共同决策,给这些零食按照好吃程度排个序。其中没吃过的零食,就靠我们推测。排好之后,倒序来吃,也就是先从好吃程度最低的零食开始。一直印象很深刻,有一个黑加仑口味的零食,在某次排序中,拔得头筹,但是最后发现它并没有想象中好吃。</p>\n\n<h4 id=\"暑假作业\">暑假作业</h4>\n\n<p>另一个故事,也是来自于我和我哥哥,不过不同于上一个例子,这次我们俩有了不一样的表现。</p>\n\n<p>记得刚上小学的时候,寒暑假里,我都是先把所有作业做完,再开始玩的。尤其印象深刻的是,炎热的夏天里,我坐着小板凳,在床边写暑假作业,非常有画面感。而忘记是从哪年开始,哥哥在假期开始后,带着我先玩,我才跟他学会了「原来还可以先玩」。</p>\n\n<h4 id=\"延迟满足的背后是意志力自控力\">延迟满足的背后,是意志力/自控力</h4>\n\n<p>「黑加仑零食」和「暑假作业」都是我很小的时候,延迟满足的例子,而且基本来自于主动选择延迟满足。</p>\n\n<p>从相对没有那么好吃的零食开始,一直吃到最好吃的零食,整个过程的体验,是越来越美妙,充满期待的。而如果反过来,则会对后面的零食,越来越没有兴趣。最后结果就是某些零食,一直到过了保质期,也不会被吃掉。</p>\n\n<p>暑假里如果先玩个痛快,最后赶作业,其实前面玩的就不够放松。而如果先把作业做完,就可以彻彻底底地疯到开学,100% 的放松状态。</p>\n\n<p>如果考虑到婴幼儿时期的家庭教育,或有影响,那么也不能说延迟满足就是可以先天具备的。但是先天还是后天并不是我们讨论的重点,本文也不是学术论文。</p>\n\n<p>延迟满足的根本,其实是自控力/意志力。和前面提到的米歇尔教授一样,另一位来自斯坦福大学的心理学家凯利·麦格尼格尔(Kelly McGonigal),她有一本书叫《意志力》,也有译作《自控力》。按我的理解,自控力就像重力势能,从自控力很低的状态想爬坡到高自控力状态,需要克服很多,而反过来却轻而易举。</p>\n\n<p>因此对于自控力,需要长期的刻意练习形成,并且不能轻易打破。只有在高自控力下,才能形成对满足感的延迟有很强的掌控能力。</p>\n\n<p>那么问个更根本的问题:延迟满足,确定是好于即时满足吗?</p>\n\n<h3 id=\"2快乐理论挑战延迟满足\">2、快乐理论,挑战延迟满足</h3>\n\n<p>记得中学时,听老师讲过一个关于「烂苹果」的小故事,给我的延迟满足习惯带来了极大的挑战。</p>\n\n<h4 id=\"烂苹果\">烂苹果</h4>\n\n<p>有一个中国老太太,和一个某国老太太(忘记是哪国了),各买了一箱苹果。一开始都有个别几个,没那么新鲜,有一点点要烂了的样子。中国老太太比较会过日子,都是先挑烂的吃。而外国老太太会享受,先挑好的吃。</p>\n\n<p>于是,中国老太太几乎每次都在吃烂苹果,因为随着时间的推移,原来新鲜的也开始变烂了。而外国老太太,先把好苹果吃完了,那些一点点烂的苹果,后来就烂得很严重,干脆就扔了。</p>\n\n<p>结果就是,中国老太太吃了大量的烂苹果,而外国老太太虽然扔了一些苹果,但她吃掉的都是好苹果。由此引申说中国人在很多事情上都是在吃烂苹果。</p>\n\n<p>当时年纪小,对这类很 SB 的瞎编故事,还缺乏足够的反驳意识,尤其是这种中国、外国的夹杂民族自卑感的瞎编故事。但确实给我留下了深刻印象,以至于我会去想:是不是我周末回家,可以先打两天红色警戒和扫雷,周日晚上再赶作业?</p>\n\n<h4 id=\"快乐理论\">快乐理论</h4>\n\n<p>从「烂苹果」的故事里,我们可以引申出一个「快乐理论」。如果用快乐值,来衡量收益,并且认为苹果随着时间推移会逐渐变烂,那么先吃烂苹果的快乐值,始终与烂苹果关联。而先吃好苹果,最后的烂苹果全部扔掉,则快乐值都与好苹果关联。</p>\n\n<p>唯一区别是数量,比如可能前者吃了 10 个烂苹果,后者吃了 5 个好苹果。每次平均快乐收益,必然是前者更高。</p>\n\n<p>但这个理论角度对吗?</p>\n\n<h3 id=\"3我们应该在哪个范畴内讨论延迟满足\">3、我们应该在哪个范畴内讨论「延迟满足」?</h3>\n\n<p>休闲娱乐,和完成任务,是完全不同的范畴。对于前者来说,其实没什么好讨论的,我们应该关注与后者。</p>\n\n<p>完成任务,关联着学习、工作、创业等等。这些是我们人生过程的基石。所以「烂苹果」引出的「快乐理论」,我们没必要去深入讨论对错,起码从「范畴」上来看,就已经错误了。</p>\n\n<h4 id=\"仅有正反馈刺激的奶头乐\">仅有正反馈刺激的奶头乐</h4>\n\n<p>而「完成任务」并不像「吃」那么本能,它需要我们去克服困难、主动思考、建立方法,过程中会遭遇负反馈与正反馈。而吃,在大多数情况下,仅有正反馈。</p>\n\n<p>而仅有正反馈的事儿,如果我们持续做它,就会进入奶头乐的陷阱。典型的奶头乐,比如刷短视频、打游戏。因为它们有持续快速的正反馈,没有负反馈,所以它们本身就是「满足」,而我们应该做的是,在大尺度的讨论范畴中学会延迟它们。或者那些混杂正反馈与负反馈的事务,我们要学会延迟那些仅有正反馈的局部。</p>\n\n<h4 id=\"延迟满足重在顺序而非时间\">延迟满足重在顺序,而非时间</h4>\n\n<p>因此,我们讨论到这里就很明显发现,延迟满足的核心是「顺序」而非「时间」。而有的言论甚至愚昧地解读为「拖延」、「不能把握机会」,这就是理解错误了。</p>\n\n<p>将仅有正反馈、靠本能即可驱动的事情,优先级排低。而把存在负反馈但又很重要的事情,优先级排高。</p>\n\n<h3 id=\"4常见的延迟满足与即时满足\">4、常见的延迟满足与即时满足</h3>\n\n<h4 id=\"初阶对手vs浅度延迟满足\">初阶对手 vs 浅度延迟满足</h4>\n\n<p>常见的即时满足,需要我们去延迟的,有这些:</p>\n\n<ul>\n <li>\n <p>睡懒觉</p>\n </li>\n <li>\n <p>过量饮食</p>\n </li>\n <li>\n <p>玩游戏/刷短视频等手机、PC 娱乐内容</p>\n </li>\n <li>\n <p>冲动情绪</p>\n </li>\n <li>\n <p>炫耀(粗俗说就是装B)</p>\n </li>\n</ul>\n\n<p>……</p>\n\n<p>这些显而易见属于即时满足的事情,如果我们放着重要事情不做,而先干这些,其实我们会很自然地产生负罪感。所以这些事情只是初阶的延迟满足对手。</p>\n\n<p>初阶对手,基本都是完全处于我们本能,来满足我们的及时行乐。</p>\n\n<h4 id=\"中阶对手vs中度延迟满足\">中阶对手 vs 中度延迟满足</h4>\n\n<p>稍高级的对手,是那些并不那么明显的,比如下面这些。</p>\n\n<ul>\n <li>\n <p>在家边看电视/视频,边学习/工作</p>\n </li>\n <li>\n <p>依赖二手知识,而怠于一手知识</p>\n </li>\n <li>\n <p>刷知乎、行业资讯 APP</p>\n </li>\n</ul>\n\n<p>……</p>\n\n<p>例子会有很多,我们仅稍微说下这三个。</p>\n\n<p>我们来看看「在家边看电视/视频,边学习/工作」。你确实在学习/工作,但是你的专注度会极大的降低。而事后你回顾,还很可能认为「我学习了一个下午啊」,其实是你看了一个下午视频,捎带着学习/工作了一下。</p>\n\n<p>再来看第二个,什么是「二手知识」呢?就是那些把严重简化、略化的专业知识或内容,比如《10 分钟读懂 XXX》,比如用网红化的视频来讲一个原本需要下功夫的专业内容。看罗振宇《时间的朋友》跨年演讲、参加混沌大学的班级学习、报名朋友圈裂变分享的学习课程…… 这些都是二手知识。二手知识的危害是,你缺乏系统性学习与思考,而把学习和思考给综艺化、娱乐化、浅显化了。</p>\n\n<p>刷知乎,会给你一种错觉:你也是精英人群(起码和他们混一个社区的)。刷行业资讯 APP(比如 36 氪、虎嗅这类),也会给你一种错觉:你也是创投达人、专业人士(起码是跟他们混在同一个平台的)。这些错觉,会让你对自我身份认知出现偏差,获得一种虚无的满足感。</p>\n\n<h4 id=\"高阶对手vs深度延迟满足\">高阶对手 vs 深度延迟满足</h4>\n\n<p>有些延迟满足,是更深层、更高阶的。面对的对手,也是更高阶的。</p>\n\n<p>高阶对手的特点,是伪装成很正确的样子,实则对于已经进阶到高阶排位赛的你来说,会是更危险的敌人。</p>\n\n<p>我这里就说一个影响很大的:</p>\n\n<p>* 急于行动</p>\n\n<p>其实当代年轻人,勤奋程度大多没有问题。但是过于勤奋,可能会形成急于行动的坏习惯。</p>\n\n<p>对于执行力差的人,「先干再说」、「Just Do It」是很好的激励口号。但是对于执行力已经很优秀的人,「三思而后行」反而成了更重要的事。</p>\n\n<p>对于行动力强的人来说,行动本身甚至可以给他带来快感。而这快感,恰恰就是一种「正反馈的满足感」,如果形成了对这种满足感的过度依赖,那么「行动」本身,就成了我们应该着力去延迟的满足,以避免思考上的「武断」,或行动上的「鲁莽」。</p>\n\n<p>我们常听到的「战术的勤奋掩盖战略的懒惰」、「纸上得来终觉浅,绝知此事要躬行」,这些激励式的口号,都容易让我们变为「急于行动」的暴躁老哥。</p>\n\n<p>但是这两者也要注意平衡,如果矫枉过正,又会变成「过于纸上谈兵」的人。</p>\n\n<h3 id=\"5延迟满足的驻留时长\">5、延迟满足的驻留时长</h3>\n\n<p>延迟满足,并不是一个「是否」的选择问题,而是一个需量化衡量的能力指标。延迟满足能力的量化,就是延迟的驻留时长。</p>\n\n<p>你在延迟驻留多久以后,就需要给予正反馈满足了?比如我们常看到「你再写完一页,我就让你玩 10 分钟」这样的家长教育小朋友的场景。这里「一页」就意味着一个驻留时长。</p>\n\n<p>对于延迟满足能力强的人,也就是驻留时间非常长的人,他可能把全部工作全做完,甚至还能反复修改检查、打补丁、升级,再进入到正反馈满足阶段(休息或做其他事情)。而驻留时间短的人,可能做了 3 个小时,完成了一部分,他就要休息一下,娱乐一下。</p>\n\n<p>这是表层的差别,更深层的差别在于心理层面。比如有的人对于情绪释放这种事儿,我曾经认识一个 19 岁当上大酒店的大堂经理的人,老板对其他员工宣传他 23 岁,是为了帮助他树立威信。而这个大堂经理,每隔一段时间就会到老板办公室来,跟老板发泄做情绪释放,而其他员工看到的,一直是一个没有情绪化的大堂经理。</p>\n\n<p>这里面就体现出了延迟满足的驻留时间,以及驻留结束后的满足获得(情绪释放)。我们很难做到无限长,所以选择合理的满足获得方式,很重要。</p>\n\n<p>而有的人,则能完全化解于无形,无论在商业谈判中的挑衅,还是下属的忤逆犯上,还是陌生人的出言不逊,他都能很好的自控、消解,这种人的驻留时间,某种意义上已经达到无限长了,也就是在某件事儿的延迟满足上已经做到完全内化了。</p>\n\n<p>其他方面也与情绪控制类似,能将长驻留的延迟满足内化为习性,对自由就会有更大的掌控。自由是什么呢?就是我们常听到的那句「自由不是你想做什么就做什么,而是你不想做什么就不做什么」。而不想做什么就不做,除了面对外部的选择权,更多是面对自己的,而这恰恰就是长驻留的延迟满足。</p>\n\n<h3 id=\"后记\">后记</h3>\n\n<p>总结一下,面对不同的「对手」,我们要做到不同深度的延迟满足。而延迟满足的驻留时间,则量化了我们在相应深度上的延迟满足能力。有意培养,刻意练习,用延迟满足来帮助自我成长,是一个长期课题,我也在路上。</p>\n\n<p>对于延迟满足的实操,如果日后我有更多梳理,会再行文。也期待与朋友们的讨论,欢迎添加我的个人微信号:sinosuperman 。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>如何从语言模型中抽样:标准采样技术和新核采样的探索</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>如何从语言模型中抽样:标准采样技术和新核采样的探索</h2>\t\t\n\t<time datetime=\"2019-05-27T15:24:58+00:00\" class=\"by-line\">27 May 2019, 杭州 | Ben Mann | [译] AI & 麦克船长 | 总计 3108 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n\n<ul>\n <li>原文链接:https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277</li>\n</ul>\n\n<p><img src=\"\" alt=\"\" /></p>\n\n<p>↑ 人类通常会选择令语言模型感到惊讶的词(Holtzman 等人,2019 年)</p>\n\n<p>像 GPT-2 这样的因果语言模型被训练来预测在给定上下文的情况下下一个单词的概率。例如,给定「我吃了美味的热 ()」,模型可能以 80% 的概率预测「狗」,以 5% 的概率预测「煎饼」等。这种结构的妙处在于它们可用于生成<strong>任意序列长度</strong>。我可以给模型「我吃了 ()」,从结果分布中抽取一个标记以获得「我吃了一个 ()」,然后再次将其放入模型以获得另一个分布和结果标记,可以一直这样重复下去。事实证明,这一代语言模型往往是,要么陷入重复的循环,要么跑题。为什么会发生这种情况,我们如何更好地采样以生成更像人类的文本?</p>\n\n<p>这篇文章是 Holtzman 等人在 2019 年对<a href=\"https://arxiv.org/abs/1904.09751\">《The Curious Case of Neural Text De generation》</a>的总结和探索。我发现它是我最近读过的最透彻和可读性最强的论文之一,所以如果这篇文章引起共鸣,一定记得去读读它!</p>\n\n<p>如果我们总是对最有可能的词进行采样,标准语言模型训练目标会让我们陷入「我不知道。我不知道。我不知道。」 这是不自然的,但现代语言模型中模型的大部分注意力只集中在最近的几个标记上。相反,流行的生成采样方法是基于从分布中采样的。但是抽样也会遇到一个问题:如果我们有 50K 个可能的选择,即使底部的 25K 个标记每个都极不可能,它们加起来可能具有例如 30% 的概率质量。这意味着对于每个样本,我们有三分之一的机会完全偏离我们的「思路」。由于前面提到的短上下文,这将导致不可恢复的错误级联,因为每个下一个单词都严重依赖于最近的错误单词。</p>\n\n<p>为了对抗尾部采样,最流行的方法是温度(temperature)采样和前 k 采样。</p>\n\n<p>温度采样的灵感来自统计热力学,其中高温意味着更有可能遇到低能量状态。在概率模型中,logits 扮演能量的角色,我们可以通过将 logits 除以温度来实现温度采样,然后将它们输入 softmax 并获得我们的采样概率。例如:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"o\">>>></span> <span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"o\">>>></span> <span class=\"kn\">import</span> <span class=\"nn\">torch.nn.functional</span> <span class=\"k\">as</span> <span class=\"n\">F</span>\n<span class=\"o\">>>></span> <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mi\">1</span><span class=\"p\">,</span><span class=\"mi\">2</span><span class=\"p\">,</span><span class=\"mi\">3</span><span class=\"p\">,</span><span class=\"mf\">4.</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">/</span><span class=\"mf\">1.5</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.0708</span><span class=\"p\">,</span> <span class=\"mf\">0.1378</span><span class=\"p\">,</span> <span class=\"mf\">0.2685</span><span class=\"p\">,</span> <span class=\"mf\">0.5229</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.0321</span><span class=\"p\">,</span> <span class=\"mf\">0.0871</span><span class=\"p\">,</span> <span class=\"mf\">0.2369</span><span class=\"p\">,</span> <span class=\"mf\">0.6439</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">/</span><span class=\"p\">.</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.0021</span><span class=\"p\">,</span> <span class=\"mf\">0.0158</span><span class=\"p\">,</span> <span class=\"mf\">0.1171</span><span class=\"p\">,</span> <span class=\"mf\">0.8650</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">/</span><span class=\"mf\">1e-6</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.</span><span class=\"p\">,</span> <span class=\"mf\">0.</span><span class=\"p\">,</span> <span class=\"mf\">0.</span><span class=\"p\">,</span> <span class=\"mf\">1.</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>或者直观点:</p>\n\n<p><img src=\"\" alt=\"\" /></p>\n\n<p>较低的温度使模型对其最佳选择更置信,而大于 1 的温度会降低置信。0 温度相当于 argmax/max likelihood,无限大温度对应均匀采样。</p>\n\n<p><strong>Top K Sampling(前 k 采样)</strong>意味着按概率排序并将第 k 个标记以下的任何事物的概率归零。它似乎通过去尾并使其不太可能偏离主题来提升效果。但在某些情况下,确实有很多我们可以合理地从中采样的词(下面的广泛分布),而在某些情况下则没有(下面的狭窄分布)。</p>\n\n<p><img src=\"\" alt=\"\" /></p>\n\n<p>↑ 霍尔兹曼等人 2019</p>\n\n<p>为了解决这个问题,作者提出了「top p sampling」,又名「nucleus sampling」,其中我们计算累积分布并在 CDF 超过 P 时立即切断。在上面的广泛分布示例中,可能需要前 100 个令牌超过 top_p = .9。在窄分布中,我们的样本分布中可能已经超过了 top_p = .9,只有“热”和“暖”。通过这种方式,我们仍然可以避免对严重错误的标记进行采样,但可以在得分最高的标记置信度较低时保持多样性。</p>\n\n<p>为什么最大似然抽样不起作用?在训练过程中,永远不可能看到复合错误。该模型经过训练,可以根据人工生成的上下文预测下一个标记。如果它通过生成错误的分布而导致一个标记错误,则下一个标记将使用独立于上一个预测的“正确”人类生成的上下文。在生成期间,它被迫完成自己自动生成的上下文,这是它在训练期间没有考虑的设置。</p>\n\n<p>定性结果\n以下是使用 top_k=40 和上下文“I ate a delicious”的示例</p>\n\n<p>以下是使用 top_p=0.9 和相同的“我吃了一顿美味”上下文的示例:</p>\n\n<p>在这里自己试试吧!您可以在Runtime > Change runtime type中启用 GPU并获得大批量,无需额外的运行时间。</p>\n\n<p>超越论文:自动选择 p 和 k\n我发现很难确定这些样本中的哪一个更像人类。为此我设计了一个实验来确定top_k和top_p凭经验。</p>\n\n<p>我们的目标是使用 top_k 和 top_p 来最大化选择我们提供的实际下一个单词的概率。在搜索最佳 k 和 p 值时,实际上很容易通过分析确定给定样本。对于 k,我们找到出现“黄金”标记的排序索引。对于 p,我们找到黄金代币的 CDF。例如,如果上下文是“I ate a delicious hot”,而实际单词是“dog”,但模型的预测分布最有可能是“pancake”,我们将搜索概率,直到在以下位置找到“dog”索引 3。在索引 1 处,CDF 可能为 62%。在索引 3 处,CDF 可能约为 86%,因此我们将其记录为最佳 p。</p>\n\n<p>在许多示例中,我们可以计算最佳 p 和 k 值的直方图,并计算它们的汇总统计量。我在维基百科的随机部分进行了测试,上下文长度为 15。这比模型训练的长度 (1024) 短得多,但对于https://duet.li或聊天机器人等设置很常见。</p>\n\n<p>===== ks =====\n最高 29094.00\n均值 233.69\n中位数 3.00\n只有 13376.00\n===== ps =====\n最大 1.00\n均值 0.59\n中位数 0.60\n只有 13376.00\n随意在我的colab notebook中自己尝试。</p>\n\n<p>如果模型在其训练集上进行评估,则选择 top_k = 1 是最佳选择。但是由于模型稍微超出了域,因此最有可能的标记有时会出现在列表的更下方。此外,我们还有 50K 的 token 词汇表。在许多数据集中,我们永远不会看到所有标记,但模型对此并不确定。通过使用 top_p 或 top_k 将大部分概率质量归零,我们合并了我们的先验,从不选择这些从未见过的甚至在训练中的标记。</p>\n\n<p>也就是说,这种对 k 和 p 的搜索仍然在模型的世界观的背景下,因此它只是一个创可贴。我们真正想要的是修复训练。</p>\n\n<p>固定训练\n我也开始考虑改变训练目标以更好地匹配生成任务。例如,当模型生成看起来不像人类的整个序列时,我们是否可以训练某种鉴别器来惩罚模型?如何将 GAN 架构应用于非连续域并不简单。我遇到了Adversarial Text Generation without Reinforcement Learning和RL-based idea,但似乎这些还没有成为主流。我认为将这些想法应用于过去几个月席卷最先进技术的大型变形金刚会很有趣。</p>\n\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 礼狮™ LISMIS™ 巧可宝 Chocobble [12P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 礼狮™ LISMIS™ 巧可宝 Chocobble [12P]</h2>\t\t\n\t<time datetime=\"2017-12-24T06:18:03+00:00\" class=\"by-line\">24 Dec 2017, 杭州 | 麦克船长 | 总计 382 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>产品:礼狮™ LISMIS™ 巧可宝 Chocobble</li>\n <li>产地:希腊</li>\n <li>麦克船长负责产品研发、总体设计(品牌/VI/包装/视觉)</li>\n</ul>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-001.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-002.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p><img src=\"/img/design/lismis-chocobble-003.jpg\" alt=\"image\" /></p>\n\n<p>Chocobble™ - jar x 4</p>\n\n<table>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-chocobble-4jar-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></td>\n </tr>\n </tbody>\n</table>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-4jar-2.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-4jar-3.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>Chocobble™ - jar x 8</p>\n\n<table>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-chocobble-8jar-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></td>\n </tr>\n </tbody>\n</table>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-8jar-2.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-8jar-3.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>Chocobble™ - jar x 15</p>\n\n<table>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-chocobble-15jar-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></td>\n </tr>\n </tbody>\n</table>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-15jar-2.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-15jar-3.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 礼狮™ LISMIS™ 盐焗腰果黑巧克力 Dark Chocolate Covered Cashew [3P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 礼狮™ LISMIS™ 盐焗腰果黑巧克力 Dark Chocolate Covered Cashew [3P]</h2>\t\t\n\t<time datetime=\"2017-12-24T06:18:03+00:00\" class=\"by-line\">24 Dec 2017, 杭州 | 麦克船长 | 总计 92 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>产品:礼狮™ LISMIS™ 盐焗腰果黑巧克力 Dark Chocolate Covered Cashew</li>\n <li>产地:美国</li>\n <li>麦克船长负责产品研发、总体设计(品牌/VI/包装/视觉)</li>\n</ul>\n\n<p><img src=\"/img/design/lismis-cube03-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<p><img src=\"/img/design/lismis-cube03-2.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<p><img src=\"/img/design/lismis-cube03-3.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 礼狮™ LISMIS™ 品牌 VI [9P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 礼狮™ LISMIS™ 品牌 VI [9P]</h2>\t\t\n\t<time datetime=\"2017-12-01T04:56:40+00:00\" class=\"by-line\">01 Dec 2017, 杭州 | 麦克船长 | 总计 144 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>礼狮™ LISMIS™ 品牌</li>\n <li>麦克船长负责产品研发、总体设计(品牌/VI/包装/视觉)</li>\n</ul>\n\n<p><img src=\"/img/design/lismis-vi-001.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-vi-003.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-vi-006.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-vi-005.jpg\" alt=\"image\" /></td>\n <td><img src=\"/img/design/lismis-vi-004.jpg\" alt=\"image\" /></td>\n </tr>\n </tbody>\n</table>\n\n<p><img src=\"/img/design/lismis-vi-002.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-vi-007.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-vi-008.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p><img src=\"/img/design/lismis-vi-009.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>未来人工智能就是要:让普通人过上现在富豪们的生活</title>\n \t<meta name=\"description\" content=\"有很多领域,需要专业人士面对具体的问题,给出个性化的解决方案。想获取这些个性化的解决方案,就要用金钱作为交换代价。而人工智能(Artificial Intelligence)真正能够发挥巨大作用的,恰恰就是这些领域。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>未来人工智能就是要:让普通人过上现在富豪们的生活</h2>\t\t\n\t<time datetime=\"2017-02-23T18:23:33+00:00\" class=\"by-line\">23 Feb 2017, 北京 | 麦克船长 | 总计 627 字</time>\n\t<div class=\"content\">\n\t\t<p>如果我很有钱,我就会雇佣一名私人旅行助理,帮我制定旅行计划,购买叫票,预定酒店,打包行李。</p>\n\n<p>如果我很有钱,我就会雇佣一名私人造型师,帮我购买服饰鞋帽,安排各项活动应该的着装。</p>\n\n<p>如果我很有钱,我就会雇佣一名私人医生,关注我的健康状态,处理和解答一切日常医护问题。</p>\n\n<p>类似的还有私人律师,私人营养师,私人教师……</p>\n\n<h4 id=\"但是我没有钱呢\">但是我没有钱呢?</h4>\n\n<p>有很多领域,需要专业人士面对具体的问题,给出个性化的解决方案。想获取这些个性化的解决方案,就要用金钱作为交换代价。</p>\n\n<p>而人工智能(Artificial Intelligence)真正能够发挥巨大作用的,恰恰就是这些领域。</p>\n\n<p>技术的边际成本趋于零,使得私人旅行助理、私人造型师、私人医生、私人律师、私人营养师、私人教师可以低成本、高效率地解决这些问题。</p>\n\n<p>这需要云端<strong>计算能力</strong>的支持,<strong>海量数据</strong>的支撑,<strong>算法模型</strong>的发展,和<strong>产品设计</strong>上的场景化。目前来看,旅游是较早发力做 AI 旅行定制的,其他领域也都在探索。</p>\n\n<p>AI 最先颠覆掉的,就是这些领域里的低级工种。比如律师事务所里负责文书整理工作的小律师、医院里负责病例整理的小护士… 这些工作因其特别符合计算机数据处理的口味,只要信息实现比特化、计算能力足够强,就能很高效的解决。</p>\n\n<p>更进一步,当更多维度的数据被完善,更人性化、场景化的产品细节被考虑到后,这些领域被 AI 提升生产力的现象会更进一步渗透。</p>\n\n<p>未来,将会有越来越多的工作不再需要人力去完成,现在我们可以先看看富豪们都雇了哪些助理,那都是科技行业工作者的机会。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>我们是应该「断舍离」还是「念念不忘,必有回响」</title>\n \t<meta name=\"description\" content=\"如果对某事、某人、某物的执念,会对我们的人生产生负反馈,我们就应该对此事、此人、此物「断舍离」;相反,如果是正反馈,则应该「念念不忘」。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>我们是应该「断舍离」还是「念念不忘,必有回响」</h2>\t\t\n\t<time datetime=\"2017-01-31T20:59:21+00:00\" class=\"by-line\">31 Jan 2017, 北京 | 麦克船长 | 总计 2577 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<h3 id=\"引子\">引子</h3>\n\n<p>我们夸夸其谈着自己的风光往事,好不热闹。隔壁的老王也在,他一手扶在桌上,一手在胸前比划,上嘴唇一碰下嘴唇,段子信手拈来,逗得我们捧腹不已,笑声绕廊。老韩也开始讲起了衡水的传说,每次都不重样儿。猛兄靠在门框上,笑得眼镜在鼻梁上乱颤。</p>\n\n<p>宿舍里,只有来自黄冈的老朱一人,默不作声,不时看两眼手中的书,不时又看着正在说话的人,被逗得也跟着笑起来。</p>\n\n<p>「嗨,我说老朱,讲讲你啊!」</p>\n\n<p>「我就不提啦。」</p>\n\n<p>「为什么啊?说说,说说!」</p>\n\n<p>「我不太想提高中这些事情。你们聊,我先出去一下哈。」</p>\n\n<p>「哎,真是的,行行行,你去吧…… 刚才咱们说到哪了?」</p>\n\n<p>那是十年前,我和我的大学同学们刚刚入学不久,在宿舍里一起回忆着各自高中的趣事,吹着高考高分的牛逼满天飞的日子。</p>\n\n<p>毕业后,老韩去中科院的北京某研究所读书,老王去了佐治亚(GalTech)读博士,猛兄去了伯克利(Berkeley)。老朱是我们这些人里最有出息的,他去了斯坦福(Stanford),研究火箭和空气动力学什么的。而我去了后来成为纳斯达克上市公司的华南某互联网公司。</p>\n\n<p>每个大学入学之初似乎都是这样,大家都是对高中那些事儿念念不忘,对高考的得与失记忆犹新。在多次聊天中,老朱对这类话题都打了哈哈,我就开始有些好奇。某次和老朱单独相处,聊了很久,聊开了后我问起了此事,老朱带着那么一点假正经的样子对我说:</p>\n\n<blockquote>\n <p>我给自己订了规矩,叫「不提三高」:高中、高考、高分。</p>\n</blockquote>\n\n<p>为什么?我想在读这篇文章的你,从标题可能猜到了一二。老朱解释了原因,用后来流行的话概括说,就是「<strong>断舍离</strong>」。</p>\n\n<h3 id=\"断舍离\">断舍离</h3>\n\n<p>「断舍离」一词,出自于日本作家山下英子所著的同名书籍《断 · 舍 · 离》,在此书出版后,这一词开始流行起来,那大概是在 2013 年。这词本意是指一种实操性很强的整理术。而整理术,则是带有浓重日本文化色彩的一种关于生活物品整理的方法论。</p>\n\n<p><img src=\"https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/ya2QnV41Kod8O4XB/img/725f80ab-4efc-4b06-8eeb-f29fe6afe50e.webp\" alt=\"image\" /></p>\n\n<p>进而,这种理念得以传播后,便超越了整理术的应用范畴,开始影响一些我们生活的其他方面。</p>\n\n<p>无论是老朱的「不提三高」,还是山下英子的「断舍离」,其隐含的本质都是,不要沉溺于过去。言谈,图一时口快,却把你带回过去,更囿于成败得失,而弱化了未来的规划和执行力。实物,载过往回忆,但使你常念旧日,且占据生活留白,则减少了放空的机会和轻松感。</p>\n\n<h3 id=\"念念不忘必有回响\">念念不忘,必有回响</h3>\n\n<p>与「断舍离」一词同样流行于 2013 年前后的,还有一句话,叫「念念不忘,必有回响」。这一句最早出自弘一法师的《晚晴集》。</p>\n\n<p><img src=\"https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/ya2QnV41Kod8O4XB/img/494aa186-be1b-4ee1-a2e2-8704c8434e50.webp\" alt=\"image\" /></p>\n\n<p>2013 年,王家卫导演的电影《一代宗师》中引用了这句话而使其广为传播。</p>\n\n<p>值得我们「念念不忘」的,必是让我们在未来的生活中更能感到或力量、或幸福、或希望的憧憬或回忆。历史能够成书万卷,都是来自我们一代代人对过去的念念不忘,无论是坚硬的国仇家恨,还是柔软的儿女情长;科技能够加速更迭,也都来自于人类天性的好奇心驱使,不断探究已知问题的未知边界。</p>\n\n<p>但我们有时会听到不同的声音:「不要活在过去」、「不要太执着」、「学会归零」、「学会放下」…… 还有列宁老师提醒我们「忘记过去,就意味着背叛」。</p>\n\n<p>那我们什么时候应该「断舍离」,什么时候又应该「念念不忘」?有没有什么具备实操价值的方法?</p>\n\n<h3 id=\"用正负反馈来判断何时何为\">用「正负反馈」来判断何时何为?</h3>\n\n<p>一个简单的办法,就是用「正负反馈」来判断。<strong>如果对某事、某人、某物的执念,会对我们的人生产生负反馈,我们就应该对此事、此人、此物「断舍离」;相反,如果是正反馈,则应该「念念不忘」。</strong></p>\n\n<p>「断舍离」最好的例子,一定是整理术。「扔东西」会给你带来极大的快感。克制自己的购物欲,不仅省钱,也会省空间,毕竟生活于现代社会的我们,空间是何其的有限。我们辛辛苦苦赚来一平方米要好几万块的房子,当然不是为了堆放那些「将来总会用到」而其实根本不会用到的废物的。</p>\n\n<p>生命总被分成不同的旅程:中学到大学,大学到社会,国内到国外,工作到跳槽,合租到独居,单身到结婚,二人世界到家庭生活,一线员工到部门领导 …… 每次不同的人生状态跨越后,我们都会调整自己的生活节奏和作息规律,我们也更应该调整自己的心理状态。</p>\n\n<p>荣耀加身时,对我们的激励已经得到最大化,不必担心你从那次成绩中吸取的信心还没有被完全消化,而要担心它在你的心中风头过劲,以至于令你慢下脚步。</p>\n\n<p><strong>能够取得连续成功的人,往往都懂得「断舍离」</strong>。</p>\n\n<p>Yin 是我曾经的一位合伙人,在中学时获得了「国际数学奥林匹克竞赛金牌」,后来又取得美国一所顶尖大学的博士学位,并在多年后拿到很多华人留学生羡慕的美国某大学终身教职(tenure)。Yin 对探索高深技术问题,有着天生的狂热,类似这样的人我还认识不少。他们并不见得都谦虚谨慎,有得也是狂放不已,但都会保持较高的自律,很少放任自己。</p>\n\n<p>在进入新的学校后,忘掉自己曾是「全校第一」;在来到新的公司后,忘掉自己是「核心骨干」。如果这时「念念不忘」,听到的回响,肯定不是什么锣鼓喧天,也不会是鞭炮齐鸣,而不过是自己的一个屁。</p>\n\n<p>再过一年半,将是我的一位至亲离开我二十年整的日子。坦率地说,我不知道在十二岁时经受这样的打击,与在三十二岁、四十二岁时经历,有何区别,尽管我希望这来的越晚越好。但那种悲痛,真的会在几年后转变为长久的生活力量。我们的每个亲友皆有生老病死,我想很多读者朋友会明白这种转变过程。而这转变前后,我们应该怎样调节自己呢?</p>\n\n<p>倘若亲人的离世,对你打击很大,此时刻意的忘却是很难的,相反在一段时间内你都需要进行积极的心理建设。但那段时间过后,你应该「断舍离」还是「念念不忘」?我相信大多数成年人能够处理好,一般我们会把这种「想念」转化为生活的动力,他们的离去会让我们懂得更多,比如珍惜,比如完成遗志。这都是对未来的「念念不忘,必有回响」,这也便是对生活的正反馈。</p>\n\n<p>所谓「化悲痛为力量」,具体操作起来的办法,则是在<strong>痛苦初期尽量「断舍离」,尽量转移注意,尽量寻找排解出口</strong>,无论这种痛苦是来自工作、家人,还是学业、朋友。而<strong>后期开始,化为力量,才能「必有回响」</strong>。</p>\n\n<h3 id=\"后记\">后记</h3>\n\n<p>此篇的成文之日是 2017 年 2 月 1 日,农历丁酉年正月初五。在新的一年里,我也要对过往做个整理,对未来做个规划。而读完本文,不论你身处何时何地,也可以在心中整理一下,看看对哪些事、哪些人、哪些物应该「断舍离」了,而又对哪些事、哪些人、哪些物应该「念念不忘」。</p>\n\n<p>然后,我们一起等待,那声回响。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 游戏美术 Interstaller Colonial Agency [4P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 游戏美术 Interstaller Colonial Agency [4P]</h2>\t\t\n\t<time datetime=\"2016-04-20T13:09:59+00:00\" class=\"by-line\">20 Apr 2016, 北京 | 麦克船长 | 总计 91 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>游戏名称:Interstaller Colonial Agency</li>\n <li>游戏类型:Turn-based Stategy Game</li>\n <li>发行平台:iOS</li>\n <li>策划&美术:麦克船长</li>\n</ul>\n\n<p><img src=\"/img/design/ica-001.png\" alt=\"image\" /></p>\n\n<p><img src=\"/img/design/ica-004.png\" alt=\"image\" /></p>\n\n<p><img src=\"/img/design/ica-002.png\" alt=\"image\" /></p>\n\n<p><img src=\"/img/design/ica-003.png\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | Club APP [12P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | Club APP [12P]</h2>\t\t\n\t<time datetime=\"2015-12-07T08:58:54+00:00\" class=\"by-line\">07 Dec 2015, 北京 | 麦克船长 | 总计 0 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/design/club-app-smartisan.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 9:关键线程逻辑分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本文对 RTMFPServer 线程、RTMFPManager 对 RTMFPServer 的影响进行源码解读。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 9:关键线程逻辑分析</h2>\t\t\n\t<time datetime=\"2012-08-04T17:58:17+00:00\" class=\"by-line\">04 Aug 2012, 广州 | 麦克船长 | 总计 5236 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一rtmfpserver-线程的启动和等待\" id=\"markdown-toc-一rtmfpserver-线程的启动和等待\">一、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程的启动和等待</a> <ul>\n <li><a href=\"#1pocothread\" id=\"markdown-toc-1pocothread\">1、<code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code></a></li>\n <li><a href=\"#2封装一个可运行线程的类\" id=\"markdown-toc-2封装一个可运行线程的类\">2、封装一个可运行线程的类</a></li>\n <li><a href=\"#3启动-rtmfpserver-线程\" id=\"markdown-toc-3启动-rtmfpserver-线程\">3、启动 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程</a></li>\n <li><a href=\"#4rtmfpserver-线程等待\" id=\"markdown-toc-4rtmfpserver-线程等待\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程等待</a></li>\n </ul>\n </li>\n <li><a href=\"#二rtmfpmanager-对-rtmfpserver-的影响\" id=\"markdown-toc-二rtmfpmanager-对-rtmfpserver-的影响\">二、<code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 对 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的影响</a></li>\n</ul>\n\n<h3 id=\"一rtmfpserver-线程的启动和等待\">一、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程的启动和等待</h3>\n\n<h4 id=\"1pocothread\">1、<code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code></h4>\n\n<p>Cumulus 大量使用了 <code class=\"language-plaintext highlighter-rouge\">Poco</code> 的线程库。一个简单的 Poco 线程的使用实例如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">PoechantRunnable</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Runnable</span> <span class=\"p\">{</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">void</span> <span class=\"n\">run</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"c1\">// your codes</span>\n <span class=\"p\">}</span>\n<span class=\"p\">};</span>\n \n<span class=\"kt\">int</span> <span class=\"nf\">main</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">PoechantRunnable</span> <span class=\"n\">runnable</span><span class=\"p\">;</span> <span class=\"c1\">// Image that it's a gift</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Thread</span> <span class=\"kr\">thread</span><span class=\"p\">;</span> <span class=\"c1\">// And… thread is just like your girl</span>\n <span class=\"kr\">thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">runnable</span><span class=\"p\">);</span> <span class=\"c1\">// Okay, give your sweet babe the gift :)</span>\n <span class=\"kr\">thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2封装一个可运行线程的类\">2、封装一个可运行线程的类</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中实现了一个 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 类,该类继承了 <code class=\"language-plaintext highlighter-rouge\">Runnable</code>,就是上面那个 <code class=\"language-plaintext highlighter-rouge\">gift</code> 喽。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">StartableProcess</span> <span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Runnable</span><span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">StartableProcess</span><span class=\"p\">(</span><span class=\"n\">Startable</span><span class=\"o\">&</span> <span class=\"n\">startable</span><span class=\"p\">);</span>\n<span class=\"nl\">private:</span>\n <span class=\"kt\">void</span> <span class=\"n\">run</span><span class=\"p\">();</span>\n <span class=\"n\">Startable</span><span class=\"o\">&</span> <span class=\"n\">_startable</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>可以看到其中有 <code class=\"language-plaintext highlighter-rouge\">Startable& _startable</code> 引用成员,它并没有继承 <code class=\"language-plaintext highlighter-rouge\">Runnable</code>,而是封装了 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 和 <code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Thread</span> <span class=\"kr\">_thread</span><span class=\"p\">;</span>\n<span class=\"n\">StartableProcess</span> <span class=\"n\">_process</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>这里 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 封装了一个 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 成员,与 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 是有所区别的。接下俩我们看他们是怎么用的。</p>\n\n<h4 id=\"3启动-rtmfpserver-线程\">3、启动 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程</h4>\n<p>我们可以看到在 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 类的构造函数中初始化了 <code class=\"language-plaintext highlighter-rouge\">_process</code> 成员,初始化线程成员并传入线程名,设定标志域 <code class=\"language-plaintext highlighter-rouge\">(Flag Field)_stop</code> 为 <code class=\"language-plaintext highlighter-rouge\">true</code>,因为它还没有调用启动函数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">Startable</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_name</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">),</span>\n <span class=\"kr\">_thread</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">),</span>\n <span class=\"n\">_stop</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">),</span>\n <span class=\"n\">_haveToJoin</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">_process</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>初始化 <code class=\"language-plaintext highlighter-rouge\">_process</code> 时,调用 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 构造函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">StartableProcess</span><span class=\"o\">::</span><span class=\"n\">StartableProcess</span><span class=\"p\">(</span><span class=\"n\">Startable</span><span class=\"o\">&</span> <span class=\"n\">startable</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_startable</span><span class=\"p\">(</span><span class=\"n\">startable</span><span class=\"p\">){</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>传入 <code class=\"language-plaintext highlighter-rouge\">_startable</code> 的引用。在 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中所有的线程的可运行类都是继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 类的,然后通过调用 <code class=\"language-plaintext highlighter-rouge\">start()</code> 来启动,启动后会响应到 <code class=\"language-plaintext highlighter-rouge\">run()</code>。下面我们以 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程为例。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 类是继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 类的:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">RTMFPServer</span>\n <span class=\"o\">:</span> <span class=\"k\">private</span> <span class=\"n\">Gateway</span><span class=\"p\">,</span>\n <span class=\"k\">protected</span> <span class=\"n\">Handler</span><span class=\"p\">,</span>\n <span class=\"k\">private</span> <span class=\"n\">Startable</span><span class=\"p\">,</span>\n <span class=\"k\">private</span> <span class=\"n\">SocketHandler</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的构造函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">RTMFPServer</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">cores</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">Startable</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer\"</span><span class=\"p\">),</span>\n <span class=\"n\">_sendingEngine</span><span class=\"p\">(</span><span class=\"n\">cores</span><span class=\"p\">),</span>\n <span class=\"n\">_receivingEngine</span><span class=\"p\">(</span><span class=\"n\">cores</span><span class=\"p\">),</span>\n <span class=\"n\">_pCirrus</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">_handshake</span><span class=\"p\">(</span><span class=\"n\">_receivingEngine</span><span class=\"p\">,</span>\n <span class=\"n\">_sendingEngine</span><span class=\"p\">,</span>\n <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">),</span>\n <span class=\"n\">_sessions</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>其中在初始化时调用了其父类的构造函数。接下来就要启动RTMFPServer线程了。</p>\n\n<table>\n <thead>\n <tr>\n <th>所在线程</th>\n <th>调用者</th>\n <th>函数</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>主线程</td>\n <td>main(…)</td>\n <td> </td>\n </tr>\n <tr>\n <td>主线程</td>\n <td>RTMFPServer对象</td>\n <td>RTMFPServer::start()</td>\n </tr>\n <tr>\n <td>主线程</td>\n <td>RTMFPServer对象</td>\n <td>Startable::start()</td>\n </tr>\n <tr>\n <td>主线程</td>\n <td>RTMFPServer从Startable继承来的Thread成员</td>\n <td>Thread::start(…)</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象从Startable继承来的StartableProcess成员</td>\n <td>StartableProcess::run()</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象</td>\n <td>RTMFPServer::prerun()</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象</td>\n <td>Startable::prerun()</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象</td>\n <td>RTMFPServer::run()</td>\n </tr>\n </tbody>\n</table>\n\n<h4 id=\"4rtmfpserver-线程等待\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程等待</h4>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run()</code> 实现线程的持续运行,主要是依靠这两行代码:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">while</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">terminate</span><span class=\"p\">)</span>\n <span class=\"n\">handle</span><span class=\"p\">(</span><span class=\"n\">terminate</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">handle(…)</code> 函数很简单,如下只进行了 <code class=\"language-plaintext highlighter-rouge\">sleep(...)</code> 和 <code class=\"language-plaintext highlighter-rouge\">giveHandle()</code> 两个操作。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">handle</span><span class=\"p\">(</span><span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">){</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">sleep</span><span class=\"p\">()</span> <span class=\"o\">!=</span> <span class=\"n\">STOP</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">giveHandle</span><span class=\"p\">();</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span>\n <span class=\"n\">terminate</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">sleep(…)</code> 是 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 是从 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 继承而来的,声明如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">WakeUpType</span> <span class=\"nf\">sleep</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">timeout</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>定义如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">WakeUpType</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">timeout</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"n\">WakeUpType</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">WAKEUP</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">tryWait</span><span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"p\">))</span>\n <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">TIMEOUT</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">wait</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>在运行状态下,<code class=\"language-plaintext highlighter-rouge\">_stop</code> 为 <code class=\"language-plaintext highlighter-rouge\">false</code>,而默认参数 <code class=\"language-plaintext highlighter-rouge\">timeout</code> 为 0,所以会调用:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">wait</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>这个 <code class=\"language-plaintext highlighter-rouge\">_wakeUpEvent</code> 成员是一个 <code class=\"language-plaintext highlighter-rouge\">Poco::Event</code> 对象,<code class=\"language-plaintext highlighter-rouge\">Poco::Event</code> 有一个使用方式就是在调用 <code class=\"language-plaintext highlighter-rouge\">Poco::Event::wait()</code> 后,会一直等待 <code class=\"language-plaintext highlighter-rouge\">Poco::Event::set()</code> 被调用后,才会跳出 <code class=\"language-plaintext highlighter-rouge\">wait</code> 的状态。在 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中 <code class=\"language-plaintext highlighter-rouge\">set</code> 的动作是由:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">RTMFPServer::requestHandle()</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">PoolThread::push(Poco::AutoPtr<RunnableType>& pRunnable)</code></li>\n</ul>\n\n<p>执行的。</p>\n\n<h3 id=\"二rtmfpmanager-对-rtmfpserver-的影响\">二、<code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 对 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的影响</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 与 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 同样,继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">RTMFPManager</span> <span class=\"o\">:</span> <span class=\"k\">private</span> <span class=\"n\">Task</span><span class=\"p\">,</span> <span class=\"k\">private</span> <span class=\"n\">Startable</span>\n</code></pre></div></div>\n\n<p>在构造函数中将 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 对象以引用方式传入,用以初始化其 <code class=\"language-plaintext highlighter-rouge\">_server</code> 引用成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">RTMFPManager</span><span class=\"p\">(</span><span class=\"n\">RTMFPServer</span><span class=\"o\">&</span> <span class=\"n\">server</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_server</span><span class=\"p\">(</span><span class=\"n\">server</span><span class=\"p\">),</span>\n <span class=\"n\">Task</span><span class=\"p\">(</span><span class=\"n\">server</span><span class=\"p\">),</span>\n <span class=\"n\">Startable</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPManager\"</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">start</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n\n<span class=\"cm\">/* ...... */</span>\n\n<span class=\"n\">RTMFPServer</span><span class=\"o\">&</span> <span class=\"n\">_server</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">start()</code> 成员函数,是从 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 继承而来的。然后会开启一个新的名为 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的线程。然后响应到 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager::run()</code> 函数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">run</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">setPriority</span><span class=\"p\">(</span><span class=\"n\">Thread</span><span class=\"o\">::</span><span class=\"n\">PRIO_LOW</span><span class=\"p\">);</span>\n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"mi\">2000</span><span class=\"p\">)</span><span class=\"o\">!=</span><span class=\"n\">STOP</span><span class=\"p\">)</span>\n <span class=\"n\">waitHandle</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这里要强调的是,这里的 <code class=\"language-plaintext highlighter-rouge\">setPriority</code> 在 Linux 环境下会设置失败,可以参见我在 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 在 Github 上开启的 Issue #75,其中就包括这里的线程优先级设置。</p>\n\n<p>在这里我们可以看到 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的 <code class=\"language-plaintext highlighter-rouge\">handle(…)</code> 中的 <code class=\"language-plaintext highlighter-rouge\">sleep(…)</code> 是每 2 秒一次,而这是对 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程有影响的。还记得我说的 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程的 <code class=\"language-plaintext highlighter-rouge\">_wakeUpEvent</code> 成员吗?(在第一部分中)它的激活就是在 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 中进行的,所以这里这个 2 秒是会影响到 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的主循环的等待时间的。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">WakeUpType</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">timeout</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"n\">WakeUpType</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">WAKEUP</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">tryWait</span><span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"p\">))</span>\n <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">TIMEOUT</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">wait</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>你可以自行修改 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 中 <code class=\"language-plaintext highlighter-rouge\">sleep(...)</code> 的参数,这样就会调用 <code class=\"language-plaintext highlighter-rouge\">_wakeUpEvent.tryWait(timeout)</code> 了,按照指定的等待时间(即 <code class=\"language-plaintext highlighter-rouge\">timeout</code>)来进行睡眠。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的作用是什么呢?核心就在于它的 <code class=\"language-plaintext highlighter-rouge\">handle</code> 成员函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">handle</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_server</span><span class=\"p\">.</span><span class=\"n\">manage</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这里就会调用到 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::manage()</code>,所以你要在阅读 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 源码时知道 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::manage()</code> 函数并不是在 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程内运行的,而是 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 线程内运行的。它的定义如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">manage</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">manage</span><span class=\"p\">();</span>\n <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">manage</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>它实现对现有 Session 的一些管理,比如终止已经死掉的 <code class=\"language-plaintext highlighter-rouge\">Session</code>。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 8:经由服务器的 Pub/Sub 流程的关键点</title>\n \t<meta name=\"description\" content=\"Flash 客户端通过 NetConnection 与 Cumulus 建立连接,然后通过 NetStream 使用 RTMFP 发布 Audio/Video/Data(下面简称为 A/V/D) 给服务器,这个 Flash Player 就作为一个发布者(Publisher)。RTMFP 服务器接收到后给所有的订阅者(Subscribers)发送 Audio/Video/Data。本文将介绍如何经由服务器实现 Pub/Sub 流程。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 8:经由服务器的 Pub/Sub 流程的关键点</h2>\t\t\n\t<time datetime=\"2012-07-23T03:07:43+00:00\" class=\"by-line\">23 Jul 2012, 广州 | 麦克船长 | 总计 3111 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#1客户端发布publishing-on-client-side\" id=\"markdown-toc-1客户端发布publishing-on-client-side\">1、客户端发布(Publishing on client side)</a></li>\n <li><a href=\"#2服务器端server-side\" id=\"markdown-toc-2服务器端server-side\">2、服务器端(Server-side)</a></li>\n <li><a href=\"#3客户端订阅subscribing-on-client-side\" id=\"markdown-toc-3客户端订阅subscribing-on-client-side\">3、客户端订阅(Subscribing on client side)</a></li>\n <li><a href=\"#4reference\" id=\"markdown-toc-4reference\">4、Reference</a></li>\n</ul>\n\n<p>整个流程概括如下:</p>\n\n<p>Flash 客户端通过 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 与 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 建立连接,然后通过 <code class=\"language-plaintext highlighter-rouge\">NetStream</code> 使用 RTMFP 发布 Audio/Video/Data(下面简称为 A/V/D) 给服务器,这个 Flash Player 就作为一个发布者(Publisher)。RTMFP 服务器接收到后给所有的订阅者(Subscribers)发送 Audio/Video/Data。</p>\n\n<h3 id=\"1客户端发布publishing-on-client-side\">1、客户端发布(Publishing on client side)</h3>\n\n<p>通过 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 连接 RTMFP 服务器 Cumulus,可以参考<a href=\"/2012/04/10/openrtmfp-cumulus-1/\">《OpenRTMFP/Cumulus 原理及源码解读 1:入门介绍、部署与 Hello World》</a>一文。关键的一个语句如下,其中 <code class=\"language-plaintext highlighter-rouge\">nc</code> 是一个 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 对象。</p>\n\n<div class=\"language-actionscript highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">nc</span><span class=\"p\">.</span><span class=\"nx\">connect</span><span class=\"p\">(</span><span class=\"s2\">\"rtmfp://localhost:1935\"</span><span class=\"p\">)</span><span class=\"o\">;</span>\n</code></pre></div></div>\n\n<p>在连接成功后通过 NetStream 发布 Audio/Video,如下所示,其中 <code class=\"language-plaintext highlighter-rouge\">ns1</code> 是一个 <code class=\"language-plaintext highlighter-rouge\">NetStream</code> 对象。</p>\n\n<div class=\"language-actionscript highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">ns1</span><span class=\"p\">.</span><span class=\"nx\">publish</span><span class=\"p\">(</span><span class=\"s2\">\"poechant_media_flow\"</span><span class=\"p\">,</span> <span class=\"s2\">\"live\"</span><span class=\"p\">)</span><span class=\"o\">;</span>\n</code></pre></div></div>\n\n<p>根据音视频不同的需求,播放相应内容。如果是发布 Data,则使用NetStream.send()来实现。这样就完成了客户端的 A/V/D 发布</p>\n\n<h3 id=\"2服务器端server-side\">2、服务器端(Server-side)</h3>\n\n<p>Cumulus 通过 <code class=\"language-plaintext highlighter-rouge\">RTMFPReceiving</code> 这个 RTMFP 协议数据接收引擎完成一些连接建立的相关动作,以及接收数据包:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">receive</span><span class=\"p\">(</span><span class=\"n\">RTMFPReceiving</span><span class=\"o\">&</span> <span class=\"n\">rtmfpReceiving</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>该函数会在收到客户端发来请求时响应,如果是仍未建立连接的请求,则由此创建 Session(RTMFP 的核心概念之一),并取出其中的数据包。这其中有多个过程,我这里就不详述,以后会发布文章来解释。</p>\n\n<p>继续我们的话题,在RTMFPServer::receive 函数中如果是建立连接阶段,则会调用 <code class=\"language-plaintext highlighter-rouge\">Handshake</code> 类的 <code class=\"language-plaintext highlighter-rouge\">receive</code> 来做接下来的处理,这个我就不去详细分析了,因为与本文主题无关。与本文有关的是,如果是已经创建了 Session 的,则会调用:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">ServerSession</span><span class=\"o\">::</span><span class=\"n\">packetHandler</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">packet</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这是一个相对复杂的函数,会从 packet 中取出很多有用的信息。此外,比较重要的是,在我们上述情况下,会调用 Flow 类的:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Flow</span><span class=\"o\">::</span><span class=\"n\">fragmentSortedHandler</span><span class=\"p\">(</span><span class=\"n\">UInt64</span> <span class=\"n\">stage</span><span class=\"p\">,</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">fragment</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">flags</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>该函数中会对 Audio/Video/Data 分别响应不同的处理机制:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">switch</span><span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">AMF_WITH_HANDLER</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">AMF</span><span class=\"p\">:</span>\n <span class=\"n\">messageHandler</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"n\">amf</span><span class=\"p\">);</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">AUDIO</span><span class=\"p\">:</span>\n <span class=\"n\">audioHandler</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">VIDEO</span><span class=\"p\">:</span>\n <span class=\"n\">videoHandler</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n <span class=\"nl\">default:</span>\n <span class=\"n\">rawHandler</span><span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>接下来在 <code class=\"language-plaintext highlighter-rouge\">Publication</code> 中完成对所有订阅了该发布者的 Flash Players 发送信息,核心的代码为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"n\">it</span> <span class=\"o\">!=</span> <span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span> <span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"o\">-></span><span class=\"n\">pushAudioPacket</span><span class=\"p\">(</span><span class=\"n\">time</span><span class=\"p\">,</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"n\">packet</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n \n<span class=\"k\">for</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"o\">=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span><span class=\"n\">it</span><span class=\"o\">!=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span><span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"o\">-></span><span class=\"n\">pushVideoPacket</span><span class=\"p\">(</span><span class=\"n\">time</span><span class=\"p\">,</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"n\">packet</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n \n<span class=\"k\">for</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"o\">=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span><span class=\"n\">it</span><span class=\"o\">!=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span><span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"o\">-></span><span class=\"n\">pushDataPacket</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"n\">packet</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>其中的 <code class=\"language-plaintext highlighter-rouge\">_listeners</code> 就是该 <code class=\"language-plaintext highlighter-rouge\">Publication</code> 中的所有订阅者。订阅者的添加/删除是通过:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Listener</span><span class=\"o\">&</span> <span class=\"n\">addListener</span><span class=\"p\">(</span>\n <span class=\"n\">Peer</span><span class=\"o\">&</span> <span class=\"n\">peer</span><span class=\"p\">,</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">id</span><span class=\"p\">,</span>\n <span class=\"n\">FlowWriter</span><span class=\"o\">&</span> <span class=\"n\">writer</span><span class=\"p\">,</span>\n <span class=\"kt\">bool</span> <span class=\"n\">unbuffered</span><span class=\"p\">);</span>\n \n<span class=\"kt\">void</span> <span class=\"nf\">removeListener</span><span class=\"p\">(</span>\n <span class=\"n\">Peer</span><span class=\"o\">&</span> <span class=\"n\">peer</span><span class=\"p\">,</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">id</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这两个函数来实现的。</p>\n\n<p>要注意的是,在 Publication 中已经完成了向订阅者发布信息,之后虽然会响应到 Peer 及 RTMFPServer 的onAudioPacket、onVideoPacket、onDataPacket,但此时都与订阅者接收信息无关了。Cumulus 正是在RTMFPServer::onAudioPacket、RTMFPServer::onVideoPacket、RTMFPServer::onDataPacket中调用用户定制的服务(Lua 脚本实现),完成一些自定义的需求。我是在此通过直接的 C++ 功能扩展,来添加业务需求的,没有使用 Lua 脚本及 Cumulus 中的 Lua 脚本引擎,主要原因是为了提高效率。</p>\n\n<h3 id=\"3客户端订阅subscribing-on-client-side\">3、客户端订阅(Subscribing on client side)</h3>\n\n<p>订阅很简单,在 play 的时候传入正确的发布者名称即可。</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>ns2.play(\"poechant_media_flow\");\n</code></pre></div></div>\n\n<p>测试代码可以参考 Reference-1,其中的例子是关于 <code class=\"language-plaintext highlighter-rouge\">NetStream::send(…)</code> 的,用于发送 <code class=\"language-plaintext highlighter-rouge\">Data</code>,<code class=\"language-plaintext highlighter-rouge\">Audio</code> 和 <code class=\"language-plaintext highlighter-rouge\">Video</code> 的程序可以参考该例修改。</p>\n\n<p>客户端订阅后,这些信息并不会直接从发布者那里通过 P2P 的方式接收。如果想使用发布者与接受者直接连接的方式,则需要在 <code class=\"language-plaintext highlighter-rouge\">NetStream</code> 初始化的时候,传入 <code class=\"language-plaintext highlighter-rouge\">NetStream.DIRECT_CONNECTIONS</code> 参数,默认的 <code class=\"language-plaintext highlighter-rouge\">NetStream.CONNECT_TO_FMS</code> 是将数据上行到服务器再下行给所有订阅者(Subscribers)的。根据不同的应用场景,可以使用不同的方式。</p>\n\n<h3 id=\"4reference\">4、Reference</h3>\n\n<ul>\n <li>http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html#send()</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 7:Cumulus 源码的一个线程启动 Bug 及修复方法</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。Cumulus 启动后,我们可以看到有多个线程被创建,但是有时其中的个别线程没有被成功启动,本文将告诉你如何修复并解决。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 7:Cumulus 源码的一个线程启动 Bug 及修复方法</h2>\t\t\n\t<time datetime=\"2012-06-25T02:56:26+00:00\" class=\"by-line\">25 Jun 2012, 广州 | 麦克船长 | 总计 2111 字</time>\n\t<div class=\"content\">\n\t\t<p><code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中的线程都是继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code>,在其中封装 <code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code> 成员,使得一些有关线程的操作更方便。<code class=\"language-plaintext highlighter-rouge\">Startable</code> 中的 <code class=\"language-plaintext highlighter-rouge\">start</code> 函数如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_stop</span><span class=\"p\">)</span> <span class=\"c1\">// if running</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutex</span><span class=\"p\">);</span>\n \n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_haveToJoin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"n\">_haveToJoin</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span>\n <span class=\"s\">\"Try to start up a new thread inherited from Startable\"</span><span class=\"p\">);</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">_process</span><span class=\"p\">);</span>\n <span class=\"n\">_haveToJoin</span> <span class=\"o\">=</span> \n <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutexStop</span><span class=\"p\">);</span>\n <span class=\"n\">_stop</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> \n <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span>\n <span class=\"s\">\"Impossible to start the thread : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这样一个类继承 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 的话,并启动时传入自己,则会调用到 <code class=\"language-plaintext highlighter-rouge\">Startable::start()</code>,然后调用到该类自己的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数。一般来说这个函数会一个循环,以 <code class=\"language-plaintext highlighter-rouge\">SocketManager</code> 为例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">SocketManager</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"err\">…</span> \n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">running</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"err\">…</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>我们要看看这个 <code class=\"language-plaintext highlighter-rouge\">running()</code> 是怎么回事,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">bool</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">running</span><span class=\"p\">()</span> <span class=\"k\">const</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"o\">!</span><span class=\"n\">_stop</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>很简单,就是通过 <code class=\"language-plaintext highlighter-rouge\">Startable::_stop</code> 成员来判断是否还需要继续循环下去。那么这个 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 是什么时候被设置为 <code class=\"language-plaintext highlighter-rouge\">false</code> 的呢?就是上面的 <code class=\"language-plaintext highlighter-rouge\">start()</code>,这里存在的一个问题就是先 <code class=\"language-plaintext highlighter-rouge\">start</code> 线程,再设置 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 为 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>_thread.start(_process);\n_stop=false;\n</code></pre></div></div>\n\n<p>而 <code class=\"language-plaintext highlighter-rouge\">start()</code> 之后 <code class=\"language-plaintext highlighter-rouge\">run()</code> 的时候就开始通过 <code class=\"language-plaintext highlighter-rouge\">running()</code> 来判断 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 值了。所以你会在使用 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 时,发现有时候启动起来的线程个数不对。正常情况下应该有四个线程:</p>\n\n<p><img src=\"/img/src/2012-06-25-openrtmfp-cumulus-7-1.png\" alt=\"image\" /></p>\n\n<p>它们是:</p>\n\n<ul>\n <li>主线程</li>\n <li><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程</li>\n <li><code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 线程</li>\n <li><code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 线程</li>\n</ul>\n\n<p>而异常情况可能是 <code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 没有启动,甚至 <code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 都没有启动。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 没有启动的情况,这时客户端是无法接入成功的。</p>\n\n<p><img src=\"/img/src/2012-06-25-openrtmfp-cumulus-7-2.png\" alt=\"image\" /></p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 都没有启动的情况 T.T</p>\n\n<p><img src=\"/img/src/2012-06-25-openrtmfp-cumulus-7-3.png\" alt=\"image\" /></p>\n\n<p>具体是哪个线程没有启动成功可以通过 GDB 查看。</p>\n\n<p>解决办法就是将 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 的设置操作,在启动线程之前。不过要注意锁要同时移动,并且在产生异常时设置 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_stop</span><span class=\"p\">)</span> <span class=\"c1\">// if running</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutex</span><span class=\"p\">);</span>\n \n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_haveToJoin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"n\">_haveToJoin</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span>\n <span class=\"s\">\"Try to start up a new thread inherited from Startable\"</span><span class=\"p\">);</span>\n <span class=\"p\">{</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutexStop</span><span class=\"p\">);</span>\n <span class=\"n\">_stop</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">_process</span><span class=\"p\">);</span>\n <span class=\"n\">_haveToJoin</span> <span class=\"o\">=</span> \n <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> \n <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">{</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutexStop</span><span class=\"p\">);</span>\n <span class=\"n\">_stop</span> <span class=\"o\">=</span> \n <span class=\"nb\">true</span><span class=\"p\">;</span> \n <span class=\"c1\">// June 25th, 2012, Michael@YY</span>\n <span class=\"p\">}</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span>\n <span class=\"s\">\"Impossible to start the thread : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 6:独立使用 CumulusLib 的线程安全 Bug 修复方法</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。对于使用 Cumulus 来做二次开发的技术人员,CumulusLib 是一定会使用到的,但是 CumulusLib 的源码在被单独使用时是存在严重的线程安全 Bug 的,这就是本文诞生的原因。YY 的网页版流媒体技术服务端使用到 CumulusLib 时遇到了这个问题,因此修复了这个 Bug。最终的 Bug 修复很简单,但是要先理解 CumulusLib 整体线程安全问题才能确定解决方案。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 6:独立使用 CumulusLib 的线程安全 Bug 修复方法</h2>\t\t\n\t<time datetime=\"2012-06-07T15:34:18+00:00\" class=\"by-line\">07 Jun 2012, 广州 | 麦克船长 | 总计 1538 字</time>\n\t<div class=\"content\">\n\t\t<p>OpenRTMFP/Cumulus 提供了 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code> 可以供其他 RTMFP 应用使用,而不局限于 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code>。</p>\n\n<p>一般来说,Thread A 会准备好要 <code class=\"language-plaintext highlighter-rouge\">push</code> 的消息,然后 Thread A 向消息队列 <code class=\"language-plaintext highlighter-rouge\">push</code> 消息。</p>\n\n<p>但是 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code> 中实现的,是 Thread A 向消息队列 <code class=\"language-plaintext highlighter-rouge\">push</code> 消息,然后根据这个消息在队列中的指针,再向消息内填写字段。并期望如下:</p>\n\n<p><img src=\"/img/src/2012-06-07-openrtmfp-cumulus-6-1.png\" alt=\"image\" /></p>\n\n<p>由于在 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 中,一个 Client 只在一个线程内被操作,相应的 <code class=\"language-plaintext highlighter-rouge\">FlowWriter</code> 也不会出现跨线程的问题。但是如果单独使用 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code>,如果出现线程通信,并且共享 <code class=\"language-plaintext highlighter-rouge\">FlowWriter</code> 的话,就会共享消息队列,此时可能出现这种情况。</p>\n\n<p><img src=\"/img/src/2012-06-07-openrtmfp-cumulus-6-2.png\" alt=\"image\" /></p>\n\n<p>这就导致了很严重的错误,会使得进程崩溃。修正的方式,可以是将消息完全准备好之后,再放入队列,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">/*\n * author: michael\n * date: June 6th, 2012\n * type: add\n */</span>\n<span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">FlowWriter</span><span class=\"o\">::</span><span class=\"n\">createAMFMessage</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">)</span>\n \n <span class=\"c1\">// signature.empty() means that we are on the flowWriter of FlowNull</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"p\">(</span><span class=\"n\">_closed</span> <span class=\"o\">||</span> <span class=\"n\">signature</span><span class=\"p\">.</span><span class=\"n\">empty</span><span class=\"p\">()</span> <span class=\"o\">||</span> <span class=\"n\">_band</span><span class=\"p\">.</span><span class=\"n\">failed</span><span class=\"p\">()))</span> <span class=\"p\">{</span>\n <span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">pMessage</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"n\">MessageBuffered</span><span class=\"p\">();</span>\n <span class=\"n\">MessageBuffered</span><span class=\"o\">&</span> <span class=\"n\">message</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"n\">writeResponseHeader</span><span class=\"p\">(</span><span class=\"n\">message</span><span class=\"p\">.</span><span class=\"n\">rawWriter</span><span class=\"p\">,</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">pMessage</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">MessageBuffered</span><span class=\"o\">&</span> <span class=\"n\">message</span><span class=\"p\">(</span><span class=\"n\">_MessageNull</span><span class=\"p\">);</span>\n <span class=\"n\">writeResponseHeader</span><span class=\"p\">(</span><span class=\"n\">message</span><span class=\"p\">.</span><span class=\"n\">rawWriter</span><span class=\"p\">,</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">NULL</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>然后再调用时最后再增加 <code class=\"language-plaintext highlighter-rouge\">push</code> 操作:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">/*\n * author: michael\n * date: June 6th, 2012\n * type: add\n */</span>\n<span class=\"kt\">void</span> <span class=\"n\">FlowWriter</span><span class=\"o\">::</span><span class=\"n\">pushAMFMessage</span><span class=\"p\">(</span><span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">pMessage</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pMessage</span> <span class=\"o\">!=</span> <span class=\"nb\">NULL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_messages</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这样就使得消息的数据被写完了,才被放入队列中,如下:</p>\n\n<p><img src=\"/img/src/2012-06-07-openrtmfp-cumulus-6-3.png\" alt=\"image\" /></p>\n\n<p>不过如果考虑线程安全,多个线程对同一个消息队列进行操作时,就要加锁:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">/*\n * author: michael\n * date: June 6th, 2012\n * type: add\n */</span>\n<span class=\"kt\">void</span> <span class=\"n\">FlowWriter</span><span class=\"o\">::</span><span class=\"n\">pushAMFMessage</span><span class=\"p\">(</span><span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">pMessage</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pMessage</span> <span class=\"o\">!=</span> <span class=\"nb\">NULL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Mutex</span><span class=\"o\">::</span><span class=\"n\">ScopedLock</span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">msgQueueMutex</span><span class=\"p\">);</span>\n <span class=\"n\">_messages</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这样就基本解决了这个线程安全问题。</p>\n\n<p>另外,使用 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code> 要遵循 GPL 协议,一定不要忘记。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>一名出色软件工程师的技术基本功:编程与工具</title>\n \t<meta name=\"description\" content=\"再过一个多月,我就毕业工作一年了。目前在广州的 YY 语音,是 Web YY 音视频媒体技术负责人,公司预计在下半年上市,我希望通过 Web 版 YY 能为用户更容易访问(免注册、免登陆)来拉动 YY 的 DAU(活跃用户人数)助力 YY 上市。夜深人静,写一些自己对于出色软件工程师技术基本功的理解。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>一名出色软件工程师的技术基本功:编程与工具</h2>\t\t\n\t<time datetime=\"2012-05-15T17:06:59+00:00\" class=\"by-line\">15 May 2012, 广州 | 麦克船长 | 总计 2132 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<h3 id=\"0写在前面\">0、写在前面</h3>\n\n<p>再过一个多月,我就毕业工作一年了。目前在广州的 YY 语音,是 Web YY 音视频媒体技术负责人,公司预计在下半年上市,我希望通过 Web 版 YY 能为用户更容易访问(免注册、免登陆)来拉动 YY 的 DAU(活跃用户人数)助力 YY 上市。夜深人静,写一些自己对于出色软件工程师技术基本功的理解。</p>\n\n<h3 id=\"1编程\">1、编程</h3>\n\n<p>首先至少精通一门高级语言(注意是精通),然后要熟悉额外的几门语言。举例来说:</p>\n\n<h4 id=\"如果你精通c语言\">如果你精通 C 语言</h4>\n\n<p>那么除了其语言标准之外,还要精通 Linux 平台的系统 API,以及一些常用的库,还有单元测试工具。当然,如果你需要精通 C 语言的话,应该是需要你经常做与操作系统直接接触的应用底层开发,或者编写一些基础库。</p>\n\n<h4 id=\"如果你精通c语言-1\">如果你精通 C++ 语言</h4>\n\n<p>那么除了 C++ 语言标准外,你应该还要精通 STL(虽然这已经纳入 C++ 标准,但是我还是要提两句),以及一些常用的库,比如 Boost、ACE、POCO 等。</p>\n\n<p>另外,精通 C/C++ 要求你必须要会用 GCC/G++、GDB、Makefile(整合 Makefile 的 CMake 等)/Scons 等等。</p>\n\n<h4 id=\"精通的关键还是针对语言核心来说的\">精通的关键,还是针对语言核心来说的。</h4>\n\n<p>第一,你要对这个语言的语法特性熟稔;</p>\n\n<p>第二,你要对这个语言的标准库的每个 API 熟稔;</p>\n\n<p>第三,你要能够熟练运用这门语言编写各种设计模式;</p>\n\n<p>第四,你能够运用你对这门语言的掌握,完成任意给定的编程任务。</p>\n\n<p>那么,其他额外要熟悉的语言,你要做到有的放矢,就是当你要进行某种开发的时候,你在这方面能够熟练使用这门语言。比如你可以用 PHP 熟练地进行 Web 开发,你可以用 Perl 熟练地处理文本,你可以用 Bash 熟练地编写脚本小工具。</p>\n\n<h4 id=\"与计算机网络的基础结构相关联的技术实现\">与计算机、网络的基础结构相关联的技术实现</h4>\n\n<p>除了这些呢,设计模式、异步 IO、进程与线程、网络编程也是你必须精通的。当然,你只要精通你所使用的语言的这些方面的就可以了。</p>\n\n<h3 id=\"2工具\">2、工具</h3>\n\n<p>对于工具有三个层面:</p>\n\n<p>第一,是熟练的使用一些工具。</p>\n\n<p>第二,是能够发现提高生产力的工具。</p>\n\n<p>第三,是能够在无可用工具时自己编写工具。</p>\n\n<p>那么都有哪些最最最基本的工具呢?</p>\n\n<h4 id=\"ideintegrateddevelopmentenvironment\">IDE(Integrated Development Environment)</h4>\n\n<p>第一自然是 IDE,这是程序员的武器。如果你是 Windows 下的 C/C++ 开发者,建议你使用 Visual Studio,不要小看它,如果你能够精通它,你也算是一个高手。如果你是 Mac 下的C/C++/Objective-C 开发者,可以选择 XCode、Eclipse,并配合 Vim/Emacs 使用。如果你是 Linux 下的开发者,可以使用 Vim/Emacs。</p>\n\n<h4 id=\"vcsversioncontrolsystem\">VCS(Version Control System)</h4>\n\n<p>VCS 可以分为两类,一类是 CVCS(Central VCS),另一类是 DVCS(Distributed VCS)。现在 CVCS 一般使用 SVN、CVS,DVCS 一般使用 Git、Mercurial(Hg)。至于 CVCS 和 DVCS 的区别,道地谁先进,我喜欢下面这段比喻:</p>\n\n<blockquote>\n <p>Once you understand the conceptual differences between CVS/SVN and Git, and then subsequently start to use Git, you may find it very difficult to go back. You should really start to experiment only if you think you’re going to migrate in the near future, because using Git is like watching TV in colour: once you’ve discovered it, it’s really difficult to go back to black & white.</p>\n</blockquote>\n\n<p>一旦你使用了 VCS,你就会接触到 Google Code、Github、BitBucket 等等。它们其实可以算是一种在线工具。</p>\n\n<h4 id=\"clicommandlineinterface\">CLI(Command Line Interface)</h4>\n\n<p>我们一般都说命令行(Command Line),为什么还带一个「I」呢?类比 API(Application Program Interface)、GUI(Graphical User Interface)就能明白了,这都是与某个系统的交互接口,API 是通过一些 Library 调用实现交互,GUI 是通过在图形界面上的点击/拖动/滑动等实现交互。熟练地运用操作系统的 CLI。无论你是使用 Linux、Mac、Solaris、FreeBSD,甚至是 Windows,你都要熟练使用 CLI。</p>\n\n<h3 id=\"3结语\">3、结语</h3>\n\n<p>还能想到什么?由于现在夜深人静,头脑不够清醒,只能想到这些。况且在这些方面,我也达不到「精通」,甚至想去甚远。那姑且先这样吧,如果哪位朋友有什么想说的,可以在下面给我留言,我会补充到文中。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 5:IO 管理源码分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 Cumulus 中 Input/Output 管理的源码分析,包括流缓冲区、IO 流、局部内存片。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 5:IO 管理源码分析</h2>\t\t\n\t<time datetime=\"2012-04-24T03:31:10+00:00\" class=\"by-line\">24 Apr 2012, 广州 | 麦克船长 | 总计 12668 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一流缓冲区\" id=\"markdown-toc-一流缓冲区\">一、流缓冲区</a> <ul>\n <li><a href=\"#1了解-stdstreambuf\" id=\"markdown-toc-1了解-stdstreambuf\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::streambuf</code></a> <ul>\n <li><a href=\"#11单步移动内置指针\" id=\"markdown-toc-11单步移动内置指针\">1.1、单步移动内置指针</a></li>\n <li><a href=\"#12获取-get-指针和-put-指针的位置\" id=\"markdown-toc-12获取-get-指针和-put-指针的位置\">1.2、获取 get 指针和 put 指针的位置</a></li>\n <li><a href=\"#13设置-get-和-put-指针可达区域的上下界\" id=\"markdown-toc-13设置-get-和-put-指针可达区域的上下界\">1.3、设置 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针可达区域的上下界</a></li>\n </ul>\n </li>\n <li><a href=\"#2memorystreambuf\" id=\"markdown-toc-2memorystreambuf\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code></a> <ul>\n <li><a href=\"#21移动内置的-get-和-put-指针\" id=\"markdown-toc-21移动内置的-get-和-put-指针\">2.1、移动内置的 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针:</a></li>\n <li><a href=\"#22获取-get-和-put-指针当前位置\" id=\"markdown-toc-22获取-get-和-put-指针当前位置\">2.2、获取 get 和 put 指针当前位置:</a></li>\n <li><a href=\"#23获取缓冲区的起始位置和大小\" id=\"markdown-toc-23获取缓冲区的起始位置和大小\">2.3、获取缓冲区的起始位置和大小:</a></li>\n <li><a href=\"#24缓冲区的已写字节数\" id=\"markdown-toc-24缓冲区的已写字节数\">2.4、缓冲区的已写字节数</a></li>\n <li><a href=\"#25显式设定-put-和-get-指针位置\" id=\"markdown-toc-25显式设定-put-和-get-指针位置\">2.5、显式设定 <code class=\"language-plaintext highlighter-rouge\">put</code> 和 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针位置</a></li>\n <li><a href=\"#26-修改缓冲区大小\" id=\"markdown-toc-26-修改缓冲区大小\">2.6 修改缓冲区大小</a></li>\n <li><a href=\"#27构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-27构造函数拷贝构造函数和析构函数\">2.7、构造函数、拷贝构造函数和析构函数</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#二io-流\" id=\"markdown-toc-二io-流\">二、IO 流</a> <ul>\n <li><a href=\"#1了解-stdios\" id=\"markdown-toc-1了解-stdios\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::ios</code></a></li>\n <li><a href=\"#2memoryios\" id=\"markdown-toc-2memoryios\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code></a> <ul>\n <li><a href=\"#21构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-21构造函数拷贝构造函数和析构函数\">2.1、构造函数、拷贝构造函数和析构函数</a></li>\n <li><a href=\"#22得到-memorystreambuf-成员的地址\" id=\"markdown-toc-22得到-memorystreambuf-成员的地址\">2.2、得到 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的地址</a></li>\n <li><a href=\"#23当前位置\" id=\"markdown-toc-23当前位置\">2.3、当前位置</a></li>\n <li><a href=\"#24封装-memorystreambuf-成员的一些函数\" id=\"markdown-toc-24封装-memorystreambuf-成员的一些函数\">2.4、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的一些函数</a></li>\n <li><a href=\"#25-缓冲区可读数据的字节数\" id=\"markdown-toc-25-缓冲区可读数据的字节数\">2.5 缓冲区可读数据的字节数</a></li>\n </ul>\n </li>\n <li><a href=\"#3输入流\" id=\"markdown-toc-3输入流\">3、输入流</a></li>\n <li><a href=\"#4输出流\" id=\"markdown-toc-4输出流\">4、输出流</a> <ul>\n <li><a href=\"#41-构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-41-构造函数拷贝构造函数和析构函数\">4.1 构造函数、拷贝构造函数和析构函数</a></li>\n <li><a href=\"#42-读取和设定已写字节数\" id=\"markdown-toc-42-读取和设定已写字节数\">4.2 读取和设定已写字节数</a></li>\n <li><a href=\"#43-当前位置\" id=\"markdown-toc-43-当前位置\">4.3 当前位置</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#三局部内存片\" id=\"markdown-toc-三局部内存片\">三、局部内存片</a> <ul>\n <li><a href=\"#1构造函数\" id=\"markdown-toc-1构造函数\">1、构造函数</a></li>\n <li><a href=\"#2析构函数\" id=\"markdown-toc-2析构函数\">2、析构函数</a></li>\n <li><a href=\"#3缓冲区切割\" id=\"markdown-toc-3缓冲区切割\">3、缓冲区切割</a></li>\n </ul>\n </li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<p>本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 Cumulus 中 Input/Output 管理的源码分析,包括流缓冲区、IO 流、局部内存片。</p>\n\n<h3 id=\"一流缓冲区\">一、流缓冲区</h3>\n\n<p>这段我们主要分析 MemoryStream.h 文件中定义的类。</p>\n\n<h4 id=\"1了解-stdstreambuf\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::streambuf</code></h4>\n\n<p>首先要了解 <code class=\"language-plaintext highlighter-rouge\">streambuf</code> 内置了一个 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针和一个 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针。<code class=\"language-plaintext highlighter-rouge\">streambuf</code> 的所有操作基本都是对这两个指针的操作。其一些成员函数的缩写中的 <code class=\"language-plaintext highlighter-rouge\">g</code> 和 <code class=\"language-plaintext highlighter-rouge\">p</code> 就分别表示 get pointer 和 put pointer。</p>\n\n<h5 id=\"11单步移动内置指针\">1.1、单步移动内置指针</h5>\n\n<p>Increase get pointer: Advances the get pointer by <code class=\"language-plaintext highlighter-rouge\">n</code> positions. The get pointer is the internal pointer that points to the next location in the controlled input sequence.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">gbump</span> <span class=\"p\">(</span> <span class=\"kt\">int</span> <span class=\"n\">n</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>Increase put pointer: Advances the put pointer by <code class=\"language-plaintext highlighter-rouge\">n</code> positions. The put pointer is the internal pointer that points to the next location of the controlled output sequence.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">pbump</span> <span class=\"p\">(</span> <span class=\"kt\">int</span> <span class=\"n\">n</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<h5 id=\"12获取-get-指针和-put-指针的位置\">1.2、获取 get 指针和 put 指针的位置</h5>\n\n<p>Pointer to current position of input sequence: Returns a reference to the current element of the controlled input sequence (i.e., the “get pointer”).</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">char</span> <span class=\"o\">*</span> <span class=\"n\">gptr</span> <span class=\"p\">(</span> <span class=\"p\">)</span> <span class=\"k\">const</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>Pointer to current position of output sequence: Returns a reference to the current element of the output sequence (the put pointer).</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">char</span> <span class=\"o\">*</span> <span class=\"n\">pptr</span> <span class=\"p\">(</span> <span class=\"p\">)</span> <span class=\"k\">const</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<h5 id=\"13设置-get-和-put-指针可达区域的上下界\">1.3、设置 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针可达区域的上下界</h5>\n\n<p>Set input sequence pointers: Sets values for the pointers that define both the boundaries of the accessible part of the controlled input sequence and the get pointer itself.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">setg</span> <span class=\"p\">(</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gbeg</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gnext</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gend</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">gbeg</code>: New value for the pointer to the beginning of the accessible part of the controlled input sequence.\ngnext: New value for the get pointer, which points to the next element within the controlled input sequence where the next input operation shall be performed.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">gend</code>: New value for the end pointer, just past the end of the accessible part of the controlled input sequence.</li>\n <li>Set output sequence pointers: Sets the values that define the boundaries of the accessible part of the controlled output sequence.</li>\n</ul>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">setp</span> <span class=\"p\">(</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pbeg</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pend</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">pbeg</code>: New value for the pointer to the beginning of the accessible part of the controlled output sequenceand put pointer.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">pend</code>: New value for the end pointer, just past the end of the accessible part of the controlled output sequence.</li>\n</ul>\n\n<h4 id=\"2memorystreambuf\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code></h4>\n\n<p>类定义:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryStreamBuf</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">streambuf</span> <span class=\"p\">{</span>\n <span class=\"k\">friend</span> <span class=\"k\">class</span> <span class=\"nc\">ScopedMemoryClip</span><span class=\"p\">;</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">written</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">void</span> <span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">void</span> <span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">);</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">void</span> <span class=\"n\">position</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gCurrent</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pCurrent</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n \n<span class=\"nl\">private:</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">int</span> <span class=\"n\">overflow</span><span class=\"p\">(</span><span class=\"n\">int_type</span> <span class=\"n\">c</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">int</span> <span class=\"n\">underflow</span><span class=\"p\">();</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">int</span> <span class=\"n\">sync</span><span class=\"p\">();</span>\n \n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_written</span><span class=\"p\">;</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n \n <span class=\"n\">MemoryStreamBuf</span><span class=\"p\">();</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"k\">operator</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span><span class=\"p\">);</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 是 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 的友元,其内部有 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 的成员,这里暂且不管。构造函数传入的参数是缓冲区的地址和缓冲区大小(字节数)。拷贝构造函数和析构函数不必赘述。</p>\n\n<h5 id=\"21移动内置的-get-和-put-指针\">2.1、移动内置的 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针:</h5>\n\n<p><code class=\"language-plaintext highlighter-rouge\">put</code> 和 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针都移动:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">pbump</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">gbump</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"22获取-get-和-put-指针当前位置\">2.2、获取 get 和 put 指针当前位置:</h5>\n\n<p>封装 <code class=\"language-plaintext highlighter-rouge\">streambuf</code> 的 <code class=\"language-plaintext highlighter-rouge\">gptr</code> 和 <code class=\"language-plaintext highlighter-rouge\">pptr</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">gCurrent</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">gptr</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">pptr</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"23获取缓冲区的起始位置和大小\">2.3、获取缓冲区的起始位置和大小:</h5>\n\n<p>依赖于内置成员变量 pBuffer 和 bufferSize:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">begin</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">size</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"24缓冲区的已写字节数\">2.4、缓冲区的已写字节数</h5>\n\n<p>读取(其中也可能发生设置操作):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt32</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"kt\">int</span> <span class=\"n\">written</span> <span class=\"o\">=</span> <span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"c1\">// 已写字节数</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">written</span> <span class=\"o\"><</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">written</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">written</span> <span class=\"o\">></span> <span class=\"n\">_written</span><span class=\"p\">)</span> <span class=\"c1\">// 保存已写字节数</span>\n <span class=\"n\">_written</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"p\">)</span><span class=\"n\">written</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">_written</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设置:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_written</span><span class=\"o\">=</span><span class=\"n\">size</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"25显式设定-put-和-get-指针位置\">2.5、显式设定 <code class=\"language-plaintext highlighter-rouge\">put</code> 和 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针位置</h5>\n\n<p>设定 put 和 get 指针为以缓冲区首地址为开始偏移量为 pos 的位置:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">position</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n \n <span class=\"c1\">// 保存已写字节数</span>\n <span class=\"n\">written</span><span class=\"p\">();</span> <span class=\"c1\">// Save nb char written</span>\n \n <span class=\"c1\">// 移动 put 指针</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pos</span> <span class=\"o\">></span> <span class=\"n\">_bufferSize</span><span class=\"p\">)</span>\n <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n <span class=\"n\">pbump</span><span class=\"p\">((</span><span class=\"kt\">int</span><span class=\"p\">)</span> <span class=\"n\">pos</span><span class=\"p\">);</span>\n \n <span class=\"c1\">// 移动 get 指针</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">pos</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"26-修改缓冲区大小\">2.6 修改缓冲区大小</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"c1\">// 大小标识</span>\n <span class=\"n\">_bufferSize</span> <span class=\"o\">=</span> <span class=\"n\">newSize</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// gptr 当前位置</span>\n <span class=\"kt\">int</span> <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">gCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pos</span> <span class=\"o\">></span> <span class=\"n\">_bufferSize</span><span class=\"p\">)</span> <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 设置 gptr 可达范围和当前位置</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">pos</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span> \n <span class=\"c1\">// pptr 当前位置</span>\n <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pos</span> <span class=\"o\">></span> <span class=\"n\">_bufferSize</span><span class=\"p\">)</span> <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 设置 pptr 可达范围和当前位置</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">pbump</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"27构造函数拷贝构造函数和析构函数\">2.7、构造函数、拷贝构造函数和析构函数</h5>\n\n<p>构造函数会设定 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 和 <code class=\"language-plaintext highlighter-rouge\">gptr</code>,并初始化 <code class=\"language-plaintext highlighter-rouge\">pBuffer</code> 和 <code class=\"language-plaintext highlighter-rouge\">bufferSize</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span> <span class=\"n\">_pBuffer</span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">),</span><span class=\"n\">_bufferSize</span><span class=\"p\">(</span><span class=\"n\">bufferSize</span><span class=\"p\">),</span><span class=\"n\">_written</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span><span class=\"p\">,</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>析构函数会拷贝对方的 <code class=\"language-plaintext highlighter-rouge\">pBuffer</code>、<code class=\"language-plaintext highlighter-rouge\">bufferSizse</code>、<code class=\"language-plaintext highlighter-rouge\">_written</code>,并设定 <code class=\"language-plaintext highlighter-rouge\">gptr</code>、<code class=\"language-plaintext highlighter-rouge\">pptr</code>。注意设定 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 时,要分别调用 <code class=\"language-plaintext highlighter-rouge\">setp</code> 和 <code class=\"language-plaintext highlighter-rouge\">pbump</code>,因为 <code class=\"language-plaintext highlighter-rouge\">setp</code> 仅将 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 设定为传入的首个参数值(与可达范围的首地址相同)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span> <span class=\"n\">_pBuffer</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">),</span><span class=\"n\">_bufferSize</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">),</span><span class=\"n\">_written</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_written</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">gCurrent</span><span class=\"p\">(),</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">pbump</span><span class=\"p\">((</span><span class=\"kt\">int</span><span class=\"p\">)(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">pCurrent</span><span class=\"p\">()</span><span class=\"o\">-</span><span class=\"n\">_pBuffer</span><span class=\"p\">));</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>析构函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::~</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"二io-流\">二、IO 流</h3>\n\n<h4 id=\"1了解-stdios\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::ios</code></h4>\n\n<p>Initialize object [<code class=\"language-plaintext highlighter-rouge\">protected</code>]: This protected member initializes the values of the stream’s internal flags and member variables.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">init</span> <span class=\"p\">(</span> <span class=\"n\">streambuf</span><span class=\"o\">*</span> <span class=\"n\">sb</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>初始化后如下函数的返回值:</p>\n\n<table>\n <thead>\n <tr>\n <th>member function</th>\n <th>value</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>rdbuf()</td>\n <td>sb</td>\n </tr>\n <tr>\n <td>tie()</td>\n <td>0</td>\n </tr>\n <tr>\n <td>rdstate()</td>\n <td>goodbit if sb is not a null pointer, badbit otherwise</td>\n </tr>\n <tr>\n <td>exceptions()</td>\n <td>goodbit</td>\n </tr>\n <tr>\n <td>flags()</td>\n <td>skipws | dec</td>\n </tr>\n <tr>\n <td>width()</td>\n <td>0</td>\n </tr>\n <tr>\n <td>precision()</td>\n <td>6</td>\n </tr>\n <tr>\n <td>fill()</td>\n <td>‘ ’ (whitespace)</td>\n </tr>\n <tr>\n <td>getloc()</td>\n <td>a copy of locale()</td>\n </tr>\n </tbody>\n</table>\n\n<h4 id=\"2memoryios\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code></h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code> 封装 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code>,且是 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 和 <code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code>的基类,用以确保流缓冲区和基类的初始化序列的正确性。该类继承自 <code class=\"language-plaintext highlighter-rouge\">std::ios</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryIOS</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"k\">virtual</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">ios</span>\n<span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">MemoryIOS</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryIOS</span><span class=\"p\">();</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">*</span> <span class=\"n\">rdbuf</span><span class=\"p\">();</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">()</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"kt\">void</span> <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">);</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n<span class=\"nl\">private:</span>\n <span class=\"n\">MemoryStreamBuf</span> <span class=\"n\">_buf</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h5 id=\"21构造函数拷贝构造函数和析构函数\">2.1、构造函数、拷贝构造函数和析构函数</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span><span class=\"n\">_buf</span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">poco_ios_init</span><span class=\"p\">(</span><span class=\"o\">&</span><span class=\"n\">_buf</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">poco_ios_init</code> 为 <code class=\"language-plaintext highlighter-rouge\">init</code> 的宏定义,用于初始化成员 <code class=\"language-plaintext highlighter-rouge\">_buf</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">MemoryIOS</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span><span class=\"n\">_buf</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_buf</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">poco_ios_init</span><span class=\"p\">(</span><span class=\"o\">&</span><span class=\"n\">_buf</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>拷贝构造函数同构造函数。如下的析构函数不必赘述:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryIOS</span><span class=\"o\">::~</span><span class=\"n\">MemoryIOS</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"22得到-memorystreambuf-成员的地址\">2.2、得到 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的地址</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">*</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">rdbuf</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"o\">&</span><span class=\"n\">_buf</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"23当前位置\">2.3、当前位置</h5>\n\n<p>这是一个纯虚函数,由 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 和 <code class=\"language-plaintext highlighter-rouge\">MemoryOutpuStream</code> 继承时实现:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">virtual</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">()</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<h5 id=\"24封装-memorystreambuf-成员的一些函数\">2.4、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的一些函数</h5>\n\n<p><code class=\"language-plaintext highlighter-rouge\">begin</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">begin</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">resize</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">newSize</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">next</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">position</code> 封装为 <code class=\"language-plaintext highlighter-rouge\">reset</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"o\">>=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">position</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"n\">clear</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"25-缓冲区可读数据的字节数\">2.5 缓冲区可读数据的字节数</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt32</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"kt\">int</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">size</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"p\">(</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">begin</span><span class=\"p\">());</span> <span class=\"c1\">// 缓冲区剩余可读数据字节数</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">result</span> <span class=\"o\"><</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"p\">)</span><span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3输入流\">3、输入流</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryInputStream</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">MemoryIOS</span><span class=\"p\">,</span> <span class=\"k\">public</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">istream</span>\n<span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"c1\">/// Creates a MemoryInputStream for the given memory area,</span>\n <span class=\"c1\">/// ready for reading.</span>\n <span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryInputStream</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">();</span>\n <span class=\"c1\">/// Destroys the MemoryInputStream.</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>构造函数、拷贝构造函数和析构函数也都没什么可说的,初始化 <code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code> 以及 <code class=\"language-plaintext highlighter-rouge\">istream</code>。<code class=\"language-plaintext highlighter-rouge\">istream</code> 是 <code class=\"language-plaintext highlighter-rouge\">iostream</code> 中的 <code class=\"language-plaintext highlighter-rouge\">basic_istream</code> 别名(<code class=\"language-plaintext highlighter-rouge\">typedef</code>)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryInputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span> \n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"k\">const_cast</span><span class=\"o\"><</span><span class=\"kt\">char</span><span class=\"o\">*></span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">),</span> <span class=\"n\">bufferSize</span><span class=\"p\">),</span> <span class=\"n\">istream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">MemoryInputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryInputStream</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">),</span> <span class=\"n\">istream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">MemoryInputStream</span><span class=\"o\">::~</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>唯一的一个成员函数是 <code class=\"language-plaintext highlighter-rouge\">current</code>,封装了 <code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code> 的 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的 <code class=\"language-plaintext highlighter-rouge\">gCurrent</code> 函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryInputStream</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">gCurrent</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"4输出流\">4、输出流</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryOutputStream</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">MemoryIOS</span><span class=\"p\">,</span> <span class=\"k\">public</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">ostream</span>\n <span class=\"c1\">/// An input stream for reading from a memory area.</span>\n<span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"c1\">/// Creates a MemoryOutputStream for the given memory area,</span>\n <span class=\"c1\">/// ready for writing.</span>\n <span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryOutputStream</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">();</span>\n <span class=\"c1\">/// Destroys the MemoryInputStream.</span>\n \n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">written</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h5 id=\"41-构造函数拷贝构造函数和析构函数\">4.1 构造函数、拷贝构造函数和析构函数</h5>\n\n<p>如下,不赘述了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span> \n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">bufferSize</span><span class=\"p\">),</span> <span class=\"n\">ostream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n<span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryOutputStream</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">),</span> <span class=\"n\">ostream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">MemoryOutputStream</span><span class=\"o\">::~</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">(){</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"42-读取和设定已写字节数\">4.2 读取和设定已写字节数</h5>\n\n<p>读取:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">written</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设定:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"43-当前位置\">4.3 当前位置</h5>\n\n<p>与 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 中的封装类似:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">pCurrent</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"三局部内存片\">三、局部内存片</h3>\n\n<p>在第一部分的流缓冲区介绍 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 时,其中有一个名为 <code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 的友元,它就是本文所要介绍的。首先,最重要的是,<code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 中有一个 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">ScopedMemoryClip</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">ScopedMemoryClip</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">buffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">offset</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">ScopedMemoryClip</span><span class=\"p\">();</span>\n<span class=\"nl\">private:</span>\n <span class=\"kt\">void</span> <span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Int32</span> <span class=\"n\">offset</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_offset</span><span class=\"p\">;</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">_buffer</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"1构造函数\">1、构造函数</h4>\n\n<p>构造函数传入的参数对应的就是 <code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 的两个成员值。其中偏移量不能超过 <code class=\"language-plaintext highlighter-rouge\">MemoryStremamBuf</code> 的缓冲区上线值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ScopedMemoryClip</span><span class=\"o\">::</span><span class=\"n\">ScopedMemoryClip</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">offset</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_offset</span><span class=\"p\">(</span><span class=\"n\">offset</span><span class=\"p\">),</span> <span class=\"n\">_buffer</span><span class=\"p\">(</span><span class=\"n\">buffer</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_offset</span> <span class=\"o\">>=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">)</span>\n <span class=\"n\">_offset</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_offset</span> <span class=\"o\"><</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_offset</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"n\">_offset</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2析构函数\">2、析构函数</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ScopedMemoryClip</span><span class=\"o\">::~</span><span class=\"n\">ScopedMemoryClip</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"p\">(</span><span class=\"n\">Int32</span><span class=\"p\">)</span><span class=\"n\">_offset</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3缓冲区切割\">3、缓冲区切割</h4>\n\n<p>可以看到构造函数和析构函数中都调用了 <code class=\"language-plaintext highlighter-rouge\">clip</code> 函数,该函数切割完缓冲区,形成局部内存片:</p>\n\n<ul>\n <li>如果传入的偏移量参数为正,则仅保留切割之后的后一部分。</li>\n <li>如果传入的参数为负,则相当于向前扩充缓冲区(只发生于析构函数中)。其源码如下。</li>\n</ul>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">ScopedMemoryClip</span><span class=\"o\">::</span><span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"n\">Int32</span> <span class=\"n\">offset</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n \n <span class=\"c1\">// 获取到 gptr</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gpos</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">gCurrent</span><span class=\"p\">();</span>\n \n <span class=\"c1\">// 偏移缓冲区地址,并修改缓冲区大小</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+=</span> <span class=\"n\">offset</span><span class=\"p\">;</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span> <span class=\"o\">-=</span> <span class=\"n\">offset</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// pptr 的位置减去缓冲区新地址,作为 pptr 的新位置</span>\n <span class=\"kt\">int</span> <span class=\"n\">ppos</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 设置 gptr 可达区域和位置</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">gpos</span><span class=\"p\">,</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n \n <span class=\"c1\">// 设置 pptr 可达区域和位置</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">pbump</span><span class=\"p\">(</span><span class=\"n\">ppos</span><span class=\"p\">);</span>\n \n <span class=\"c1\">// 如果已写数据数小于偏移量,则可以将已写数据数设置为零</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\"><</span> <span class=\"n\">offset</span><span class=\"p\">)</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 如果已写数据数大于等于偏移量,则减去 offset</span>\n <span class=\"k\">else</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">-=</span> <span class=\"n\">offset</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 若已写字节数大于缓冲区容量,则设定为缓冲区容量</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">></span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">)</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>http://www.cplusplus.com/reference/iostream/streambuf/gbump/</li>\n <li>http://www.cplusplus.com/reference/iostream/streambuf/pbump/</li>\n <li>http://www.cplusplus.com/reference/iostream/ios/init/</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 4:AMF 解析源码分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 ActionScript 独有的 AMF 数据格式,并对其序列化和反序列化的源码进行详细解读。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 4:AMF 解析源码分析</h2>\t\t\n\t<time datetime=\"2012-04-24T02:04:55+00:00\" class=\"by-line\">24 Apr 2012, 广州 | 麦克船长 | 总计 30820 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一amf-数据类型定义\" id=\"markdown-toc-一amf-数据类型定义\">一、AMF 数据类型定义</a> <ul>\n <li><a href=\"#1数据类型\" id=\"markdown-toc-1数据类型\">1、数据类型</a></li>\n <li><a href=\"#2undefined-type\" id=\"markdown-toc-2undefined-type\">2、<code class=\"language-plaintext highlighter-rouge\">undefined</code> Type</a></li>\n <li><a href=\"#3null-type\" id=\"markdown-toc-3null-type\">3、<code class=\"language-plaintext highlighter-rouge\">null</code> Type</a></li>\n <li><a href=\"#4false-type\" id=\"markdown-toc-4false-type\">4、<code class=\"language-plaintext highlighter-rouge\">false</code> type</a></li>\n <li><a href=\"#5true-type\" id=\"markdown-toc-5true-type\">5、<code class=\"language-plaintext highlighter-rouge\">true</code> type</a></li>\n <li><a href=\"#6integer-type\" id=\"markdown-toc-6integer-type\">6、<code class=\"language-plaintext highlighter-rouge\">integer</code> type</a></li>\n <li><a href=\"#7double-type\" id=\"markdown-toc-7double-type\">7、<code class=\"language-plaintext highlighter-rouge\">double</code> type</a></li>\n <li><a href=\"#8string-type\" id=\"markdown-toc-8string-type\">8、<code class=\"language-plaintext highlighter-rouge\">String</code> type</a></li>\n <li><a href=\"#9xmldocument-type\" id=\"markdown-toc-9xmldocument-type\">9、<code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> type</a></li>\n <li><a href=\"#10date-type\" id=\"markdown-toc-10date-type\">10、<code class=\"language-plaintext highlighter-rouge\">Date</code> type</a></li>\n <li><a href=\"#11array-type\" id=\"markdown-toc-11array-type\">11、<code class=\"language-plaintext highlighter-rouge\">Array</code> type</a></li>\n <li><a href=\"#12object-type\" id=\"markdown-toc-12object-type\">12、<code class=\"language-plaintext highlighter-rouge\">Object</code> type</a></li>\n <li><a href=\"#13xml-type\" id=\"markdown-toc-13xml-type\">13、<code class=\"language-plaintext highlighter-rouge\">XML</code> type</a></li>\n <li><a href=\"#14bytearray-type\" id=\"markdown-toc-14bytearray-type\">14、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> type</a></li>\n <li><a href=\"#15amf3-的使用\" id=\"markdown-toc-15amf3-的使用\">15、AMF3 的使用</a> <ul>\n <li><a href=\"#151netconnection-and-amf-3\" id=\"markdown-toc-151netconnection-and-amf-3\">15.1、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> and AMF 3</a></li>\n <li><a href=\"#152netconnection-in-actionscript-30\" id=\"markdown-toc-152netconnection-in-actionscript-30\">15.2、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> in ActionScript 3.0</a></li>\n <li><a href=\"#153bytearray-idatainput-and-idataoutput\" id=\"markdown-toc-153bytearray-idatainput-and-idataoutput\">15.3、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code>, <code class=\"language-plaintext highlighter-rouge\">IDataInput</code> and <code class=\"language-plaintext highlighter-rouge\">IDataOutput</code></a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#二binaryreaderwriter\" id=\"markdown-toc-二binaryreaderwriter\">二、<code class=\"language-plaintext highlighter-rouge\">BinaryReader/Writer</code></a> <ul>\n <li><a href=\"#1amf3-数据格式基础\" id=\"markdown-toc-1amf3-数据格式基础\">1、AMF3 数据格式基础</a></li>\n <li><a href=\"#2序列化\" id=\"markdown-toc-2序列化\">2、序列化</a></li>\n <li><a href=\"#3反序列化\" id=\"markdown-toc-3反序列化\">3、反序列化</a></li>\n </ul>\n </li>\n <li><a href=\"#三packetreaderwriter\" id=\"markdown-toc-三packetreaderwriter\">三、<code class=\"language-plaintext highlighter-rouge\">PacketReader/Writer</code></a> <ul>\n <li><a href=\"#1packetreader\" id=\"markdown-toc-1packetreader\">1、PacketReader</a> <ul>\n <li><a href=\"#11封装-memoryinputstream\" id=\"markdown-toc-11封装-memoryinputstream\">1.1、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code></a></li>\n <li><a href=\"#12收缩缓冲区\" id=\"markdown-toc-12收缩缓冲区\">1.2、收缩缓冲区</a></li>\n <li><a href=\"#13构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-13构造函数拷贝构造函数和析构函数\">1.3、构造函数、拷贝构造函数和析构函数</a></li>\n </ul>\n </li>\n <li><a href=\"#2packetwriter\" id=\"markdown-toc-2packetwriter\">2、<code class=\"language-plaintext highlighter-rouge\">PacketWriter</code></a> <ul>\n <li><a href=\"#21封装memoryoutputstream\" id=\"markdown-toc-21封装memoryoutputstream\">2.1、封装<code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code></a></li>\n <li><a href=\"#22封装-binarywriter\" id=\"markdown-toc-22封装-binarywriter\">2.2、封装 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code></a></li>\n <li><a href=\"#23构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-23构造函数拷贝构造函数和析构函数\">2.3、构造函数、拷贝构造函数和析构函数</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#四amfreader\" id=\"markdown-toc-四amfreader\">四、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code></a> <ul>\n <li><a href=\"#1objectdef\" id=\"markdown-toc-1objectdef\">1、<code class=\"language-plaintext highlighter-rouge\">ObjectDef</code></a></li>\n <li><a href=\"#2amfreader-定义\" id=\"markdown-toc-2amfreader-定义\">2、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code> 定义</a> <ul>\n <li><a href=\"#21构造函数析构函数\" id=\"markdown-toc-21构造函数析构函数\">2.1、构造函数、析构函数</a></li>\n <li><a href=\"#22简单封装-packetreader-的一些函数\" id=\"markdown-toc-22简单封装-packetreader-的一些函数\">2.2、简单封装 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code> 的一些函数</a></li>\n <li><a href=\"#23设置-gptr-位置\" id=\"markdown-toc-23设置-gptr-位置\">2.3、设置 <code class=\"language-plaintext highlighter-rouge\">gptr</code> 位置</a></li>\n <li><a href=\"#24判断类型\" id=\"markdown-toc-24判断类型\">2.4、判断类型</a></li>\n </ul>\n </li>\n <li><a href=\"#3解析-as3-null\" id=\"markdown-toc-3解析-as3-null\">3、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Null</code></a></li>\n <li><a href=\"#4解析-as3-number\" id=\"markdown-toc-4解析-as3-number\">4、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Number</code></a></li>\n <li><a href=\"#5解析-as3-integer\" id=\"markdown-toc-5解析-as3-integer\">5、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Integer</code></a></li>\n <li><a href=\"#6解析-as3-boolean\" id=\"markdown-toc-6解析-as3-boolean\">6、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Boolean</code></a></li>\n <li><a href=\"#7开始引用与结束引用\" id=\"markdown-toc-7开始引用与结束引用\">7、开始引用与结束引用</a></li>\n <li><a href=\"#8解析-as3-bytearray\" id=\"markdown-toc-8解析-as3-bytearray\">8、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code></a></li>\n <li><a href=\"#9解析-as3-date\" id=\"markdown-toc-9解析-as3-date\">9、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Date</code></a></li>\n <li><a href=\"#10解析-as3-dictionary\" id=\"markdown-toc-10解析-as3-dictionary\">10、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Dictionary</code></a></li>\n </ul>\n </li>\n</ul>\n\n<p>本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 ActionScript 独有的 AMF 数据格式,并对其序列化和反序列化的源码进行详细解读。</p>\n\n<h3 id=\"一amf-数据类型定义\">一、AMF 数据类型定义</h3>\n\n<h4 id=\"1数据类型\">1、数据类型</h4>\n\n<p>各种数据类型的标示都在 AMF.h 中定义为宏</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">#define AMF_NUMBER 0x00 // 浮点数\n#define AMF_BOOLEAN 0x01 // 布尔型\n#define AMF_STRING 0x02 // 字符串\n#define AMF_BEGIN_OBJECT 0x03 // 对象,开始\n#define AMF_NULL 0x05 // null\n#define AMF_UNDEFINED 0x06\n#define AMF_REFERENCE 0x07\n#define AMF_MIXED_ARRAY 0x08\n#define AMF_END_OBJECT 0x09 // 对象,结束\n#define AMF_BEGIN_TYPED_OBJECT 0x10\n#define AMF_STRICT_ARRAY 0x0A\n#define AMF_DATE 0x0B // 日期\n#define AMF_LONG_STRING 0x0C // 字符串\n#define AMF_UNSUPPORTED 0x0D\n</span> \n<span class=\"cp\">#define AMF_AVMPLUS_OBJECT 0x11\n#define AMF_END 0xFF\n</span> \n<span class=\"cp\">#define AMF3_UNDEFINED 0x00\n#define AMF3_NULL 0x01\n#define AMF3_FALSE 0x02\n#define AMF3_TRUE 0x03\n#define AMF3_INTEGER 0x04\n#define AMF3_NUMBER 0x05\n#define AMF3_STRING 0x06\n#define AMF3_DATE 0x08\n#define AMF3_ARRAY 0x09\n#define AMF3_OBJECT 0x0A\n#define AMF3_BYTEARRAY 0x0C\n#define AMF3_DICTIONARY 0x11\n</span></code></pre></div></div>\n\n<p>并定义了一个枚举类表示数据类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">AMF</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"k\">enum</span> <span class=\"n\">Type</span> <span class=\"p\">{</span>\n <span class=\"n\">Null</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">Boolean</span><span class=\"p\">,</span>\n <span class=\"n\">Integer</span><span class=\"p\">,</span>\n <span class=\"n\">Number</span><span class=\"p\">,</span>\n <span class=\"n\">String</span><span class=\"p\">,</span>\n <span class=\"n\">Date</span><span class=\"p\">,</span>\n <span class=\"n\">Array</span><span class=\"p\">,</span>\n <span class=\"n\">Object</span><span class=\"p\">,</span>\n <span class=\"n\">ByteArray</span><span class=\"p\">,</span>\n <span class=\"n\">Dictionary</span><span class=\"p\">,</span>\n <span class=\"n\">RawObjectContent</span><span class=\"p\">,</span>\n <span class=\"n\">End</span>\n <span class=\"p\">};</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"2undefined-type\">2、<code class=\"language-plaintext highlighter-rouge\">undefined</code> Type</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">undefined</code> 类型由 <code class=\"language-plaintext highlighter-rouge\">undefined</code> 类型标记表示。此值不会编码任何其他信息。</p>\n\n<h4 id=\"3null-type\">3、<code class=\"language-plaintext highlighter-rouge\">null</code> Type</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">null</code> 类型由 <code class=\"language-plaintext highlighter-rouge\">null</code> 类型标记表示。此值不会编码任何其他信息。</p>\n\n<h4 id=\"4false-type\">4、<code class=\"language-plaintext highlighter-rouge\">false</code> type</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">false</code> 类型由 <code class=\"language-plaintext highlighter-rouge\">false</code> 类型标记表示,用于编码布尔值 <code class=\"language-plaintext highlighter-rouge\">false</code>。注意,在 ActionScript 3.0 中,布尔值的原始形式和对象形式不存在。此值不会编码任何其他信息。</p>\n\n<h4 id=\"5true-type\">5、<code class=\"language-plaintext highlighter-rouge\">true</code> type</h4>\n\n<p>true 类型由 true 类型标记表示,用于编码布尔值 true。注意,在 ActionScript 3.0 中,布尔值的原始形式和对象形式不存在。此值不会编码任何其他信息。</p>\n\n<h4 id=\"6integer-type\">6、<code class=\"language-plaintext highlighter-rouge\">integer</code> type</h4>\n\n<p>在 AMF 3 中,整数使用可变长度的无符号 29 位整数进行序列化。ActionScript 3.0 中的整数类型 - 有符号 <code class=\"language-plaintext highlighter-rouge\">int</code> 类型和无符号 <code class=\"language-plaintext highlighter-rouge\">uint</code> 类型 - 也使用 29 位在 AVM+中表示。如果无符号整数 (<code class=\"language-plaintext highlighter-rouge\">uint</code>) 的值大于等于 229 或者如果有符号整数 (<code class=\"language-plaintext highlighter-rouge\">int</code>) 的值大于等于 228,则它将被 AVM+ 表示为 <code class=\"language-plaintext highlighter-rouge\">double</code> 类型,并使用 AMF 3 double 类型进行序列化。</p>\n\n<h4 id=\"7double-type\">7、<code class=\"language-plaintext highlighter-rouge\">double</code> type</h4>\n\n<p>AMF 3 的 <code class=\"language-plaintext highlighter-rouge\">double</code> 类型与 AMF 0 的 <code class=\"language-plaintext highlighter-rouge\">Number</code> 类型编码方式相同。此类型用于编码 ActionScript <code class=\"language-plaintext highlighter-rouge\">Number</code> 或值大于等于 228 的 ActionScript <code class=\"language-plaintext highlighter-rouge\">int</code> 或值大于等于 229 的 ActionScript <code class=\"language-plaintext highlighter-rouge\">uint</code>。编码值始终是网络字节顺序中的 8 字节 IEEE-754 双精度浮点值 (低内存中的符号位)。</p>\n\n<h4 id=\"8string-type\">8、<code class=\"language-plaintext highlighter-rouge\">String</code> type</h4>\n\n<p>ActionScript String 值使用 AMF 3 中的单个 string 类型表示 - AMF 0 中的 <code class=\"language-plaintext highlighter-rouge\">string</code> 和 <code class=\"language-plaintext highlighter-rouge\">long string</code> 类型的概念不再使用。可以使用对隐式字符串引用表中的索引将字符串作为先前发生的字符串的引用发送。字符串使用 UTF-8 编码 - 但是头可以描述字符串文本或字符串引用。空字符串永远不会作为引用发送。</p>\n\n<h4 id=\"9xmldocument-type\">9、<code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> type</h4>\n\n<p>ActionScript 3.0 引入了新的 XML 类型 (参见 3.13),但是旧版的 XMLDocument 类型在语言中被保留为 <code class=\"language-plaintext highlighter-rouge\">flash.xml.XMLDocument</code>。与 AMF 0 类似,<code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> 的结构需要扁平化为字符串表示以进行序列化。与 AMF 中的其他字符串一样,内容使用 UTF-8 编码。XMLDocuments 可以通过使用对隐式对象引用表中的索引作为先前发生的 <code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> 实例的引用发送。</p>\n\n<h4 id=\"10date-type\">10、<code class=\"language-plaintext highlighter-rouge\">Date</code> type</h4>\n\n<p>在 AMF 3 中,ActionScript Date 简单地作为自 1970 年 1 月 1 日午夜 (UTC 时区) 以来的毫秒数进行序列化。不发送本地时区信息。可以使用对隐式对象引用表中的索引将日期作为先前发生的日期实例的引用发送。</p>\n\n<h4 id=\"11array-type\">11、<code class=\"language-plaintext highlighter-rouge\">Array</code> type</h4>\n\n<p>ActionScript 数组的类型和在数组中的位置是基于它们的索引性质描述的。以下表格概述了这些术语的含义:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">strict</code>:仅包含序数(数字)索引</li>\n <li><code class=\"language-plaintext highlighter-rouge\">dense</code>:序数索引从 0 开始,并且在连续索引之间不存在间隙(即,从 0 到数组长度的每一个索引都被定义了)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sparse</code>:包含至少两个索引之间的一个间隙</li>\n <li><code class=\"language-plaintext highlighter-rouge\">associative</code>:包含至少一个非序数(字符串)索引(有时称为 ECMA 数组)</li>\n</ul>\n\n<p>AMF 将数组分为两部分,密集部分和关联部分。关联部分的二进制表示由名称/值对(可能没有)终止的空字符串。密集部分的二进制表示由密集部分的大小(可能为零)以及有序的值列表(可能没有)组成。在 AMF 中写入的顺序是密集部分的大小,一个以空字符串终止的名称/值对列表,然后是大小的值。数组可以通过使用隐式对象引用表的索引作为先前发生的数组的引用来发送。</p>\n\n<h4 id=\"12object-type\">12、<code class=\"language-plaintext highlighter-rouge\">Object</code> type</h4>\n\n<p>AMF 3 中有一种类型用于处理 ActionScript 对象和自定义用户类。使用术语 “traits” 来描述类的定义特征。除了 “anonymous” 对象和 “typed” 对象,ActionScript 3.0 还引入了两个进一步的 traits 来描述如何序列化对象,即 “dynamic” 和 “externalizable”。以下表格概述了这些术语和它们的含义:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">Anonymous</code>:实际的 ActionScript 对象类型的实例或没有注册别名的类的实例(在反序列化时将其视为对象)。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">Typed</code>:具有注册别名的类的实例。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">Dynamic</code>:具有动态特征声明的类定义的实例;可以在运行时动态地从实例中添加和删除公共变量成员。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">Externalizable</code>:实现 flash.utils.IExternalizable 的类的实例,它完全控制其成员的序列化(特征信息中不包含属性名)。</li>\n</ul>\n\n<p>在这些特征之外,对象的特征信息还可能包括在类上定义的一组公共变量和公共可读写属性名称(即不是函数的公共成员)。成员名称的顺序很重要,因为在特征信息之后的成员值将按照完全相同的顺序出现。这些成员被视为密封成员,因为它们是由类型明确定义的。</p>\n\n<p>如果类型是动态的,则在密封成员之后可以包括一个进一步的部分,该部分将动态成员列为名称/值对。当遇到空字符串名称时,继续读取动态成员。</p>\n\n<p>对象可以通过使用隐式对象引用表中的索引来作为先前发生对象的引用。此外,还可以通过使用隐式特征引用表中的索引将特征信息发送为先前发生的一组特征的引用。</p>\n\n<h4 id=\"13xml-type\">13、<code class=\"language-plaintext highlighter-rouge\">XML</code> type</h4>\n\n<p>ActionScript 3.0 引入了一种新的 <code class=\"language-plaintext highlighter-rouge\">XML</code> 类型,支持 E4X 语法。为了序列化,需要将 <code class=\"language-plaintext highlighter-rouge\">XML</code> 类型展平成字符串表示形式。与 AMF 中的其他字符串一样,内容使用 UTF-8 编码。<code class=\"language-plaintext highlighter-rouge\">XML</code> 实例可以通过使用对隐式对象引用表中的索引作为先前发生的 XML 实例的引用发送。请注意,这种编码对 <code class=\"language-plaintext highlighter-rouge\">XML</code> 的使用造成了一些理论限制。每个 UTF-8 编码的 <code class=\"language-plaintext highlighter-rouge\">XML</code> 实例的字节长度最大为 228-1 字节(大约 256 MB)。</p>\n\n<h4 id=\"14bytearray-type\">14、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> type</h4>\n\n<p>用于保存字节数组,即 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code>。AMF 3 使用可变长度编码 29 位整数序列化此类型,其中包括字节长度前缀,然后是 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 的原始字节。<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 实例可以通过使用对隐式对象引用表中的索引作为先前发生的 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 实例的引用发送。</p>\n\n<h4 id=\"15amf3-的使用\">15、AMF3 的使用</h4>\n\n<h5 id=\"151netconnection-and-amf-3\">15.1、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> and AMF 3</h5>\n\n<p>除了序列化 ActionScript 类型外,AMF 还可用于远程服务的异步调用。可使用简单的消息结构将一批请求发送到远程端点。此消息结构的格式为 AMF 0(参见[AMF0])。可以使用特殊的 <code class=\"language-plaintext highlighter-rouge\">avmplus-object-marker</code> 类型将上下文头值或消息正文切换到 AMF 3 编码。</p>\n\n<h5 id=\"152netconnection-in-actionscript-30\">15.2、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> in ActionScript 3.0</h5>\n\n<p>在 ActionScript 3.0 中,NetConnection 的限定类名是 flash.net.NetConnection。这个类仍然使用响应器来处理远程端点的结果和状态响应,但是现在需要强类型的 Responder 类。完全限定的类名是 flash.net.Responder。除了正常的结果和状态响应之外,NetConnection 还会分发事件,开发人员可以添加监听器。下面是这些事件的概述:</p>\n\n<ul>\n <li>当异常异步抛出时触发,例如来自本机异步代码。</li>\n <li>当输入或输出错误导致网络操作失败时触发。</li>\n <li>当 NetConnection 对象报告其状态或错误条件时触发。</li>\n <li>如果对 NetConnection.call() 的调用尝试连接到调用者安全沙箱外的服务器,则会触发。</li>\n</ul>\n\n<h5 id=\"153bytearray-idatainput-and-idataoutput\">15.3、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code>, <code class=\"language-plaintext highlighter-rouge\">IDataInput</code> and <code class=\"language-plaintext highlighter-rouge\">IDataOutput</code></h5>\n\n<p>ActionScript 3.0 引入了一种新类型,用于支持以字节数组形式处理原始数据,即 <code class=\"language-plaintext highlighter-rouge\">flash.utils.ByteArray</code>。为了协助 ActionScript 对象序列化和复制,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 实现了 <code class=\"language-plaintext highlighter-rouge\">flash.utils.IDataInput</code> 和 <code class=\"language-plaintext highlighter-rouge\">flash.utils.IDataOutput</code>。这些接口指定了帮助将常见类型写入字节流的实用方法。两个感兴趣的方法是 <code class=\"language-plaintext highlighter-rouge\">IDataOutput.writeObject</code> 和 <code class=\"language-plaintext highlighter-rouge\">IDataInput.readObject</code>。这些方法使用 AMF 编码对象。使用的 AMF 版本由 <code class=\"language-plaintext highlighter-rouge\">ByteArray.objectEncoding</code> 方法控制,该方法可以设置为 AMF 3 或 AMF 0。枚举类型 <code class=\"language-plaintext highlighter-rouge\">flash.net.ObjectEncoding</code> 包含 AMF 版本的常量:分别为 <code class=\"language-plaintext highlighter-rouge\">ObjectEncoding.AMF0</code> 和 <code class=\"language-plaintext highlighter-rouge\">ObjectEncoding.AMF3</code>。</p>\n\n<p>请注意,<code class=\"language-plaintext highlighter-rouge\">ByteArray.writeObject</code> 使用一个版本的 AMF 对整个对象进行编码。与 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 不同,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 不会从 AMF 0 开始,然后将 <code class=\"language-plaintext highlighter-rouge\">objectEncoding</code> 属性设置为 AMF 3 并切换到 AMF 3。还请注意,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 为每个 <code class=\"language-plaintext highlighter-rouge\">readObject</code> 和 <code class=\"language-plaintext highlighter-rouge\">writeObject</code> 调用使用新的对象、对象特征和字符串的隐式引用表。</p>\n\n<h3 id=\"二binaryreaderwriter\">二、<code class=\"language-plaintext highlighter-rouge\">BinaryReader/Writer</code></h3>\n\n<h4 id=\"1amf3-数据格式基础\">1、AMF3 数据格式基础</h4>\n\n<p>首先介绍一下变长整数(Variable Length Integer),比如 UInt32 如下。</p>\n\n<p><img src=\"/img/src/2012-04-24-openrtmfp-cumulus-4-1.png\" alt=\"image\" /></p>\n\n<p>上图摘自 Adobe AMF3 官方文档,这是一种压缩方式的整数存储,且每一字节都对后面的数据具有预知作用。那么字符串如何处理呢?下面是字符串的处理方式,AMF0 和 AMF3 都才用 UTF-8 编码方式,并做如下压缩处理:</p>\n\n<p><img src=\"/img/src/2012-04-24-openrtmfp-cumulus-4-2.png\" alt=\"image\" /></p>\n\n<p>上图摘自 Adobe AMF3 官方文档。</p>\n\n<h4 id=\"2序列化\">2、序列化</h4>\n\n<p>序列化包括 8 位、16 位、32 位,以及 UTF-8 和 UTF-16(I guess)编码的 String,还有原生数据(Raw Data)、变长无符号整数(Variable Length Unsigned Integer)以及 IP 地址。所谓序列化就是按照指定格式编写各种对象、基础数据类型值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">BinaryWriter</span> <span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">ostream</span><span class=\"o\">&</span> <span class=\"n\">ostr</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">BinaryWriter</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write32</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write7BitValue</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write7BitLongValue</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt64</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeAddress</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Address</span><span class=\"o\">&</span> <span class=\"n\">address</span><span class=\"p\">,</span><span class=\"kt\">bool</span> <span class=\"n\">publicFlag</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeAddress</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Net</span><span class=\"o\">::</span><span class=\"n\">SocketAddress</span><span class=\"o\">&</span> <span class=\"n\">address</span><span class=\"p\">,</span><span class=\"kt\">bool</span> <span class=\"n\">publicFlag</span><span class=\"p\">);</span>\n <span class=\"k\">static</span> <span class=\"n\">BinaryWriter</span> <span class=\"n\">BinaryWriterNull</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>请注意其中名为 <code class=\"language-plaintext highlighter-rouge\">BinaryWriterNull</code> 的成员。构造函数定义为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">ostream</span><span class=\"o\">&</span> <span class=\"n\">ostr</span><span class=\"p\">)</span><span class=\"o\">:</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">ostr</span><span class=\"p\">,</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">NETWORK_BYTE_ORDER</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n\n<span class=\"n\">BinaryWriter</span><span class=\"o\">::~</span><span class=\"n\">BinaryWriter</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">flush</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>其中 <code class=\"language-plaintext highlighter-rouge\">writeRaw</code> 是简单地封装 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryWriter::writeRaw()</code>,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">((</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入整数实现如下,用的是从 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryReader</code> 继承来的重载运算符操作:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span> \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write32</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入字符串:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt16</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入变长整数,这段代码含义也一目了然,就是读取变长无符号 32 位整数、64 位整数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write7BitValue</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">shift</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">Get7BitValueSize</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">)</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">max</span> <span class=\"o\">=</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">21</span><span class=\"p\">)</span> <span class=\"p\">{</span> <span class=\"c1\">// 4 bytes maximum</span>\n <span class=\"n\">shift</span> <span class=\"o\">=</span> <span class=\"mi\">22</span><span class=\"p\">;</span>\n <span class=\"n\">max</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">7</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"mh\">0x80</span> <span class=\"o\">|</span> <span class=\"p\">((</span><span class=\"n\">value</span><span class=\"o\">>></span><span class=\"n\">shift</span><span class=\"p\">)</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">));</span>\n <span class=\"n\">shift</span> <span class=\"o\">-=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">max</span> <span class=\"o\">?</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0xFF</span> <span class=\"o\">:</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write7BitLongValue</span><span class=\"p\">(</span><span class=\"n\">UInt64</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">shift</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">Get7BitValueSize</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">)</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">max</span> <span class=\"o\">=</span> <span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">63</span><span class=\"p\">;</span> <span class=\"c1\">// Can give 10 bytes!</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">max</span><span class=\"p\">)</span>\n <span class=\"o\">++</span><span class=\"n\">shift</span><span class=\"p\">;</span>\n \n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">7</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"mh\">0x80</span> <span class=\"o\">|</span> <span class=\"p\">((</span><span class=\"n\">value</span><span class=\"o\">>></span><span class=\"n\">shift</span><span class=\"p\">)</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">));</span>\n <span class=\"n\">shift</span> <span class=\"o\">-=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">max</span> <span class=\"o\">?</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0xFF</span> <span class=\"o\">:</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入 IP 地址的两个函数暂略。</p>\n\n<h4 id=\"3反序列化\">3、反序列化</h4>\n\n<p>反序列化就是从指定格式的数据中读出各类型的数据值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">BinaryReader</span> <span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">istream</span><span class=\"o\">&</span> <span class=\"n\">istr</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">BinaryReader</span><span class=\"p\">();</span>\n \n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt64</span> <span class=\"n\">read7BitLongValue</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">read7BitEncoded</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">,</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString8</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString16</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">read16</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">read32</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readAddress</span><span class=\"p\">(</span><span class=\"n\">Address</span><span class=\"o\">&</span> <span class=\"n\">address</span><span class=\"p\">);</span>\n \n <span class=\"k\">static</span> <span class=\"n\">BinaryReader</span> <span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>构造与析构函数都很简单:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">istream</span><span class=\"o\">&</span> <span class=\"n\">istr</span><span class=\"p\">)</span> <span class=\"o\">:</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">istr</span><span class=\"p\">,</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">NETWORK_BYTE_ORDER</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">BinaryReader</span><span class=\"o\">::~</span><span class=\"n\">BinaryReader</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读取原生数据(Raw Data):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">((</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">,</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">,</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写整数,用的是 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryWriter</code> 的重载运算符:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write32</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读写整数依旧使用从 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryReader</code> 继承来的运算符操作:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt8</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read8</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\">>></span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">UInt16</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read16</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt16</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\">>></span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">UInt32</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read32</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\">>></span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写字符串:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt16</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读取变长整数,分别针对 <code class=\"language-plaintext highlighter-rouge\">UInt32</code> 和 <code class=\"language-plaintext highlighter-rouge\">UInt64</code>,要理解 <code class=\"language-plaintext highlighter-rouge\">AMF3</code> 的变长整数才能理解这个:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt32</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read7BitValue</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">while</span> <span class=\"p\">((</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x80</span><span class=\"p\">)</span> <span class=\"o\">&&</span> <span class=\"n\">n</span> <span class=\"o\"><</span> <span class=\"mi\">3</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"p\">(</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"o\">++</span><span class=\"n\">n</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"p\">((</span><span class=\"n\">n</span><span class=\"o\"><</span><span class=\"mi\">3</span><span class=\"p\">)</span> <span class=\"o\">?</span> <span class=\"mi\">7</span> <span class=\"o\">:</span> <span class=\"mi\">8</span><span class=\"p\">);</span> <span class=\"c1\">// Use all 8 bits from the 4th byte</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"n\">b</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt64</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read7BitLongValue</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"n\">UInt64</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">while</span> <span class=\"p\">((</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x80</span><span class=\"p\">)</span> <span class=\"o\">&&</span> <span class=\"n\">n</span> <span class=\"o\"><</span> <span class=\"mi\">8</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"p\">(</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"o\">++</span><span class=\"n\">n</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"p\">((</span><span class=\"n\">n</span><span class=\"o\"><</span><span class=\"mi\">8</span><span class=\"p\">)</span> <span class=\"o\">?</span> <span class=\"mi\">7</span> <span class=\"o\">:</span> <span class=\"mi\">8</span><span class=\"p\">);</span> <span class=\"c1\">// Use all 8 bits from the 4th byte</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"n\">b</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"三packetreaderwriter\">三、<code class=\"language-plaintext highlighter-rouge\">PacketReader/Writer</code></h3>\n\n<h4 id=\"1packetreader\">1、PacketReader</h4>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>#define PACKETRECV_SIZE 2048\nclass PacketReader: public BinaryReader {\npublic:\n PacketReader(const Poco::UInt8* buffer,Poco::UInt32 size);\n PacketReader(PacketReader&);\n virtual ~PacketReader();\n const Poco::UInt32 fragments;\n Poco::UInt32 available(); // 可读字节数\n Poco::UInt8* current();\n Poco::UInt32 position(); // 获取当前的相对位置(相对于起始位置的)\n void reset(Poco::UInt32 newPos = 0); // 设定当前位置\n void shrink(Poco::UInt32 rest);\n void next(Poco::UInt32 size);\nprivate:\n MemoryInputStream _memory;\n};\n</code></pre></div></div>\n\n<h6 id=\"11封装-memoryinputstream\">1.1、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code></h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">available</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">current</code>:当前绝对位置(内存地址)</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">position</code>:当前位置(绝对位置)减去缓冲区起始位置</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">position</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">next</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"12收缩缓冲区\">1.2、收缩缓冲区</h6>\n\n<p>封装了 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 的 <code class=\"language-plaintext highlighter-rouge\">resize</code>。不过由于前面的 <code class=\"language-plaintext highlighter-rouge\">if</code> 语句影响,传给 resize 的参数一定不会大于缓冲区的当前大小。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">shrink</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">rest</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">rest</span> <span class=\"o\">></span> <span class=\"n\">available</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"rest %u more upper than available %u bytes\"</span><span class=\"p\">,</span><span class=\"n\">rest</span><span class=\"p\">,</span><span class=\"n\">available</span><span class=\"p\">());</span>\n <span class=\"n\">rest</span> <span class=\"o\">=</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">position</span><span class=\"p\">()</span> <span class=\"o\">+</span> <span class=\"n\">rest</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"13构造函数拷贝构造函数和析构函数\">1.3、构造函数、拷贝构造函数和析构函数</h6>\n\n<p>构造函数先调用父类 <code class=\"language-plaintext highlighter-rouge\">BinaryReader</code> 的构造函数,并初始化 <code class=\"language-plaintext highlighter-rouge\">fragments</code> 和 <code class=\"language-plaintext highlighter-rouge\">_memory</code> 输入流的缓冲区。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">PacketReader</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">buffer</span><span class=\"p\">,</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_memory</span><span class=\"p\">((</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">size</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">fragments</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"c1\">// Consctruction by copy</span>\n<span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">PacketReader</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_memory</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">fragments</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">fragments</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">PacketReader</span><span class=\"o\">::~</span><span class=\"n\">PacketReader</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2packetwriter\">2、<code class=\"language-plaintext highlighter-rouge\">PacketWriter</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">PacketWriter</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">BinaryWriter</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">buffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"n\">PacketWriter</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">PacketWriter</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">length</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">good</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">clear</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">limit</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">length</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">flush</span><span class=\"p\">();</span>\n<span class=\"nl\">private:</span>\n <span class=\"n\">MemoryOutputStream</span> <span class=\"n\">_memory</span><span class=\"p\">;</span>\n <span class=\"n\">PacketWriter</span><span class=\"o\">*</span> <span class=\"n\">_pOther</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_size</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h6 id=\"21封装memoryoutputstream\">2.1、封装<code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code></h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">available</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">good</code>:不过 <code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code> 也是封装的 <code class=\"language-plaintext highlighter-rouge\">std::ostream</code> 的 <code class=\"language-plaintext highlighter-rouge\">good</code> 函数,True if no error flags are set.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">bool</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">good</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">good</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">written</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">length</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">position</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">position</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">()</span><span class=\"o\">-</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code>:设置缓冲区的指针位置,即 <code class=\"language-plaintext highlighter-rouge\">position</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">next</code>:移动缓冲区指针</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">begin</code>:返回缓冲区的起始地址</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">begin</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">clear</code>:其实就是修改 written 和 position,使得指定位置后面的数据在以后写的时候可以被覆盖,并不是真正的清除。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">clear</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">limit</code>:封装 <code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code> 的 <code class=\"language-plaintext highlighter-rouge\">resize</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">limit</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">length</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">length</span> <span class=\"o\">==</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">length</span> <span class=\"o\">=</span> <span class=\"n\">_size</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">length</span> <span class=\"o\">></span> <span class=\"n\">_size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"Limit '%d' more upper than buffer size '%d' bytes\"</span><span class=\"p\">,</span><span class=\"n\">length</span><span class=\"p\">,</span><span class=\"n\">_size</span><span class=\"p\">);</span>\n <span class=\"n\">length</span> <span class=\"o\">=</span> <span class=\"n\">_size</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">length</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"22封装-binarywriter\">2.2、封装 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code></h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">flush</code>:封装 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code> 的 <code class=\"language-plaintext highlighter-rouge\">flush</code>,不过 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code> 的 <code class=\"language-plaintext highlighter-rouge\">flush</code> 实际上是从 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryWriter</code> 继承而来的。其作用是「Flushes the underlying stream」。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">flush</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_pOther</span> <span class=\"o\">&&</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"n\">_pOther</span><span class=\"o\">-></span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">())</span>\n <span class=\"n\">_pOther</span><span class=\"o\">-></span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">());</span>\n <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">flush</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"23构造函数拷贝构造函数和析构函数\">2.3、构造函数、拷贝构造函数和析构函数</h6>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_memory</span><span class=\"p\">((</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">size</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">_pOther</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">_size</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"c1\">// Consctruction by copy</span>\n<span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"n\">PacketWriter</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_pOther</span><span class=\"p\">(</span><span class=\"o\">&</span><span class=\"n\">other</span><span class=\"p\">),</span>\n <span class=\"n\">_memory</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">_size</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>注意析构函数中会进行 <code class=\"language-plaintext highlighter-rouge\">flush</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PacketWriter</span><span class=\"o\">::~</span><span class=\"n\">PacketWriter</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">flush</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"四amfreader\">四、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code></h3>\n\n<h4 id=\"1objectdef\">1、<code class=\"language-plaintext highlighter-rouge\">ObjectDef</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">ObjectDef</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span> \n <span class=\"n\">ObjectDef</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">amf3</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">arrayType</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">amf3</span><span class=\"p\">(</span><span class=\"n\">amf3</span><span class=\"p\">),</span>\n <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">dynamic</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">externalizable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">count</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">arrayType</span><span class=\"p\">(</span><span class=\"n\">arrayType</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">list</span><span class=\"o\"><</span><span class=\"n\">string</span><span class=\"o\">></span> <span class=\"n\">hardProperties</span><span class=\"p\">;</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">reset</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">dynamic</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">externalizable</span><span class=\"p\">;</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">count</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">arrayType</span><span class=\"p\">;</span>\n <span class=\"k\">const</span> <span class=\"n\">UInt32</span> <span class=\"n\">amf3</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"2amfreader-定义\">2、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code> 定义</h4>\n\n<p>其中 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code> 作为其成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">AMFReader</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">AMFReader</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">reader</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">AMFReader</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">readSimpleObject</span><span class=\"p\">(</span><span class=\"n\">AMFSimpleObject</span><span class=\"o\">&</span> <span class=\"n\">object</span><span class=\"p\">);</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">read</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">double</span> <span class=\"n\">readNumber</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Int32</span> <span class=\"n\">readInteger</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readBoolean</span><span class=\"p\">();</span>\n <span class=\"n\">BinaryReader</span><span class=\"o\">&</span> <span class=\"n\">readByteArray</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">&</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Timestamp</span> <span class=\"n\">readDate</span><span class=\"p\">();</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">readObject</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readArray</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readDictionary</span><span class=\"p\">(</span><span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">weakKeys</span><span class=\"p\">);</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">readKey</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">readValue</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">readItem</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">);</span>\n <span class=\"n\">BinaryReader</span><span class=\"o\">&</span> <span class=\"n\">readRawObjectContent</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">readNull</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">startReferencing</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">stopReferencing</span><span class=\"p\">();</span>\n \n <span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">reader</span><span class=\"p\">;</span>\n \n<span class=\"nl\">private:</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">list</span><span class=\"o\"><</span><span class=\"n\">ObjectDef</span><span class=\"o\">*></span> <span class=\"n\">_objectDefs</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_stringReferences</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_classDefReferences</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_references</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_amf0References</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_amf0Reset</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_reset</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_amf3</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">_referencing</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h5 id=\"21构造函数析构函数\">2.1、构造函数、析构函数</h5>\n\n<p>参数为 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code>,会初始化一些成员变量。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">AMFReader</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">reader</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">reader</span><span class=\"p\">(</span><span class=\"n\">reader</span><span class=\"p\">),</span>\n <span class=\"n\">_reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">_amf3</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">_amf0Reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">_referencing</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>析构时,会逐一释放 <code class=\"language-plaintext highlighter-rouge\">_objectDefs</code> 中对象的内存:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">AMFReader</span><span class=\"o\">::~</span><span class=\"n\">AMFReader</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">list</span><span class=\"o\"><</span><span class=\"n\">ObjectDef</span><span class=\"o\">*>::</span><span class=\"n\">iterator</span> <span class=\"n\">it</span><span class=\"p\">;</span>\n <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"n\">it</span><span class=\"o\">!=</span><span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span> <span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span>\n <span class=\"k\">delete</span> <span class=\"o\">*</span><span class=\"n\">it</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"22简单封装-packetreader-的一些函数\">2.2、简单封装 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code> 的一些函数</h5>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code>:操作指针位置</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_reset</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_reset</span><span class=\"p\">);</span>\n <span class=\"n\">_reset</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">available</code>:根据当前缓冲区大小和 <code class=\"language-plaintext highlighter-rouge\">written</code> 计算得到</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">bool</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">current</code>:<code class=\"language-plaintext highlighter-rouge\">gptr</code> 内存地址</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"o\">*</span><span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"23设置-gptr-位置\">2.3、设置 <code class=\"language-plaintext highlighter-rouge\">gptr</code> 位置</h5>\n\n<p>其实 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 也被影响了,但是在 <code class=\"language-plaintext highlighter-rouge\">AMFReader</code> 中只用 <code class=\"language-plaintext highlighter-rouge\">gptr</code>。调用构造函数的时候,<code class=\"language-plaintext highlighter-rouge\">reset</code> 被设为 0,其后在每次读取数据的时候都会影响 <code class=\"language-plaintext highlighter-rouge\">reset</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_reset</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_reset</span><span class=\"p\">);</span>\n <span class=\"n\">_reset</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"24判断类型\">2.4、判断类型</h5>\n\n<p>分析请看注释:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">followingType</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先 <code class=\"language-plaintext highlighter-rouge\">reset</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span> <span class=\"o\">!=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_amf3</span> <span class=\"o\">=</span> <span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">back</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">amf3</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>是 AMF0 类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span>\n <span class=\"n\">_amf3</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>如果没有可读数据了,则返回 AMF::End。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">available</span><span class=\"p\">())</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">End</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>开始读了,先读到的表示 AMF 数据类型。要注意的是调用 current 并不改变指针的位置,所以你会在线面看到调用 next。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt8</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n \n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_amf3</span> <span class=\"o\">&&</span> <span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF_AVMPLUS_OBJECT</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"n\">_amf3</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">available</span><span class=\"p\">())</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">End</span><span class=\"p\">;</span>\n <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>AMF3 类型</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">switch</span><span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>Undefined 和 null 都当做 null。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF3_UNDEFINED</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_NULL</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>false 和 true 都是 boolean。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF3_FALSE</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_TRUE</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Boolean</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_INTEGER</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Integer</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_NUMBER</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_STRING</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">String</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_DATE</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Date</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_ARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Array</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_DICTIONARY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Dictionary</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_OBJECT</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Object</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_BYTEARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">ByteArray</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>落到 default 手里的话,就跳过这个字节,读取下一个。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"nl\">default:</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Unknown AMF3 type %.2x\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"err\">}</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>AMF0 类型</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">switch</span> <span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">undefined</code> 和 <code class=\"language-plaintext highlighter-rouge\">null</code> 都是 <code class=\"language-plaintext highlighter-rouge\">null</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_UNDEFINED</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_NULL</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">;</span>\n \n <span class=\"k\">case</span> <span class=\"n\">AMF_BOOLEAN</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Boolean</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_NUMBER</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">long string</code> 和 <code class=\"language-plaintext highlighter-rouge\">string</code> 都是 <code class=\"language-plaintext highlighter-rouge\">string</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_LONG_STRING</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_STRING</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">String</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">mixed array</code> 和 <code class=\"language-plaintext highlighter-rouge\">strict array</code> 都是 <code class=\"language-plaintext highlighter-rouge\">array</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_MIXED_ARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_STRICT_ARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Array</span><span class=\"p\">;</span>\n \n <span class=\"k\">case</span> <span class=\"n\">AMF_DATE</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Date</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">begin object</code> 和 <code class=\"language-plaintext highlighter-rouge\">begin typed object</code> 都是 <code class=\"language-plaintext highlighter-rouge\">object</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_BEGIN_OBJECT</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_BEGIN_TYPED_OBJECT</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Object</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是引用,就跳过表示类型值的这个字节。这个先留下来,带我们分析完 readArray 和 readObject 再回头看。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_REFERENCE</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"n\">UInt16</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read16</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">reference</span> <span class=\"o\">></span> <span class=\"n\">_amf0References</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF0 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_amf0Reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_amf0References</span><span class=\"p\">[</span><span class=\"n\">reference</span><span class=\"p\">]);</span>\n <span class=\"k\">return</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果没了,或者不支持,或者都不是,就跳过这个字节,递归继续读取:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_END_OBJECT</span><span class=\"p\">:</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF end object type without begin object type before\"</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_UNSUPPORTED</span><span class=\"p\">:</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"Unsupported type in AMF format\"</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"k\">default</span><span class=\"o\">:</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Unknown AMF type %.2x\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"err\">}</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">followingType</code> 是这个类的核心,每个具体的数据类型的分析都依赖于它的判断。这些类型的解析,会在下一篇文章中介绍。</p>\n\n<h4 id=\"3解析-as3-null\">3、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Null</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readNull</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先 reset 一下是惯例,就像糗百上的割一样。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span> \n</code></pre></div></div>\n\n<p>AMF 数据类型</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>如果是 <code class=\"language-plaintext highlighter-rouge\">Null</code>,跳过该字节,并返回</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>判断错误</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Null type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"4解析-as3-number\">4、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Number</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">double</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readNumber</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Null</code> 会被悲催的跳过:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>不是 <code class=\"language-plaintext highlighter-rouge\">Number</code> 呀 :(</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Number type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过该字节吧</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>返回吧,返回之前还用到 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryReader</code> 的运算符,注意 AS3 中的 <code class=\"language-plaintext highlighter-rouge\">Number</code> 就是 C++ 的 <code class=\"language-plaintext highlighter-rouge\">double</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">double</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"5解析-as3-integer\">5、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Integer</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Int32</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readInteger</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code> 类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>Null 的话:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>不是 <code class=\"language-plaintext highlighter-rouge\">Integer</code> 或者 Number 的话。。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Integer</span> <span class=\"o\">&&</span> <span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Integer type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过吧。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>终于是 <code class=\"language-plaintext highlighter-rouge\">Number</code> 了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kt\">double</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">Int32</span><span class=\"p\">)</span><span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读一个变长的 32 位无符号整数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// Forced in AMF3 here!</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">value</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>如果大于 3.5 个字节所能表示的最大无符号整数值(<code class=\"language-plaintext highlighter-rouge\">268435455</code> 是 <code class=\"language-plaintext highlighter-rouge\">0xFFFFFFF</code>),则减去 <code class=\"language-plaintext highlighter-rouge\">0x2FFFFFFF</code>(这还不是太理解,有能解释的朋友给留个言,或者发 email 给我 )</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">value</span> <span class=\"o\">></span> <span class=\"mi\">268435455</span><span class=\"p\">)</span>\n <span class=\"n\">value</span> <span class=\"o\">-=</span> <span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\"><<</span> <span class=\"mi\">29</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"6解析-as3-boolean\">6、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Boolean</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readBoolean</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>如果是 <code class=\"language-plaintext highlighter-rouge\">Null</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>居然不是 <code class=\"language-plaintext highlighter-rouge\">Boolean</code> 的话。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Boolean</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Boolean type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果是 <code class=\"language-plaintext highlighter-rouge\">AMF3</code> 的话,返回 <code class=\"language-plaintext highlighter-rouge\">true</code> 或者 <code class=\"language-plaintext highlighter-rouge\">false</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read8</span><span class=\"p\">()</span><span class=\"o\">==</span> <span class=\"n\">AMF3_FALSE</span> <span class=\"o\">?</span> <span class=\"nb\">false</span> <span class=\"o\">:</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>不是 <code class=\"language-plaintext highlighter-rouge\">AMF3</code> 就是 <code class=\"language-plaintext highlighter-rouge\">AMF0</code> 喽:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read8</span><span class=\"p\">()</span><span class=\"o\">==</span><span class=\"mh\">0x00</span> <span class=\"o\">?</span> <span class=\"nb\">false</span> <span class=\"o\">:</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"7开始引用与结束引用\">7、开始引用与结束引用</h4>\n\n<p>如下这两个函数会在 <code class=\"language-plaintext highlighter-rouge\">FlowConnection</code> 中调用。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">startReferencing</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_referencing</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">stopReferencing</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_referencing</span> <span class=\"o\">=</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"8解析-as3-bytearray\">8、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code></h4>\n\n<p>先回顾一下 AMF3 中的ByteArray 的数据格式:</p>\n\n<p>注意到,首先要读取一个变长无符号 32 位整数,但是最低位是 1,只有 28 位用于表示数据长度。解释完这里,下面的解析过程才好理解。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">BinaryReader</span><span class=\"o\">&</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readByteArray</span><span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Null</code> 就返回 <code class=\"language-plaintext highlighter-rouge\">BinaryReaderNull</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果不是 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code>,也返回 <code class=\"language-plaintext highlighter-rouge\">BinaryReaderNull</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">ByteArray</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF ByteArray type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过这个字节:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>注意 position 返回的是相对位置,与 AS3 中一样。<code class=\"language-plaintext highlighter-rouge\">reference</code> 表示这个地址(简单说,引用就是地址嘛)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>读取一个变长 32 位无符号整数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>最低位是 1 的话,<code class=\"language-plaintext highlighter-rouge\">isInline</code> 是 <code class=\"language-plaintext highlighter-rouge\">true</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">bool</span> <span class=\"n\">isInline</span> <span class=\"o\">=</span> <span class=\"n\">size</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>右移一位,因为那一位是标志位,上面解释过了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">>>=</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果 <code class=\"language-plaintext highlighter-rouge\">isInline</code> 是 <code class=\"language-plaintext highlighter-rouge\">true</code>,表示是 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isInline</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>如果 <code class=\"language-plaintext highlighter-rouge\">_referencing</code> 为 <code class=\"language-plaintext highlighter-rouge\">true</code> 的话(这是一个 <code class=\"language-plaintext highlighter-rouge\">vector</code>),push back this reference:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_referencing</span><span class=\"p\">)</span>\n <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">reference</span><span class=\"p\">);</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>不符合 ByteArray 的格式定义的话:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">size</span> <span class=\"o\">></span> <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF3 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>移动到这个 reference 的位置,<code class=\"language-plaintext highlighter-rouge\">_references[size]</code> 就是这个位置(相对)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_references</span><span class=\"p\">[</span><span class=\"n\">size</span><span class=\"p\">]);</span> <span class=\"c1\">// TODO size 作为索引,还没有完全理解</span>\n</code></pre></div></div>\n\n<p>读取这个 reference 的 size 值给 size对象(注意 size 是这个函数传入的引用参数,其值可以被修改)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">()</span> <span class=\"o\">>></span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>把读取完 ByteArraty 的 PacketReader 返回:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>最后强调一点,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 的数据段最大长度为 228 -1 字节,约为 256 MB。</p>\n\n<h4 id=\"9解析-as3-date\">9、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Date</code></h4>\n\n<p>先看下 <code class=\"language-plaintext highlighter-rouge\">Date</code> 的数据格式:</p>\n\n<p>下面开始分析:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Timestamp</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readDate</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>Null 的话,就返回当前时间:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">Timestamp</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果不是 Date 类型,也返回当前时间:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Date</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Date type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">Timestamp</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"kt\">double</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是 AMF3:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先读取 flag,最低一位必须是 1,其他位丢到垃圾桶。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">flags</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>当前相对位置。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>是 1 就 push back 到 <code class=\"language-plaintext highlighter-rouge\">_references</code> 里。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">bool</span> <span class=\"n\">isInline</span> <span class=\"o\">=</span> <span class=\"n\">flags</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isInline</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_referencing</span><span class=\"p\">)</span>\n <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">reference</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>读取一个 double,到 result 里(result 也是 double 类型哦~)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>如果标志位不是 1,麻烦不少哒。。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">flags</span> <span class=\"o\">>>=</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果 flag 超了,就返回当前时间作为时间戳作为 Date。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">flags</span> <span class=\"o\">></span> <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF3 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">Timestamp</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这段与 ByteArray 那段一样:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_references</span><span class=\"p\">[</span><span class=\"n\">flags</span><span class=\"p\">]);</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>返回喽~</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"nf\">Timestamp</span><span class=\"p\">((</span><span class=\"n\">Timestamp</span><span class=\"o\">::</span><span class=\"n\">TimeVal</span><span class=\"p\">)</span> <span class=\"n\">result</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">);</span>\n <span class=\"err\">}</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>读俩,因为是 double(64 位):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">);</span> <span class=\"c1\">// Timezone, useless</span>\n</code></pre></div></div>\n\n<p>返回喽~</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"nf\">Timestamp</span><span class=\"p\">((</span><span class=\"n\">Timestamp</span><span class=\"o\">::</span><span class=\"n\">TimeVal</span><span class=\"p\">)</span> <span class=\"n\">result</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"10解析-as3-dictionary\">10、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Dictionary</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readDictionary</span><span class=\"p\">(</span><span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">weakKeys</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>下面这段咱就略了。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Dictionary</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Dictionary type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过 type:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// AMF3</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span> <span class=\"c1\">// marker</span>\n</code></pre></div></div>\n\n<p>当前相对位置值作为 <code class=\"language-plaintext highlighter-rouge\">reference</code>,再读个 <code class=\"language-plaintext highlighter-rouge\">size</code>,还是最低位必须为 1,不是就返回 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">isInline</span> <span class=\"o\">=</span> <span class=\"n\">size</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n <span class=\"n\">size</span> <span class=\"o\">>>=</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">isInline</span> <span class=\"o\">&&</span> <span class=\"n\">size</span><span class=\"o\">></span><span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF3 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>下面要调用到 <code class=\"language-plaintext highlighter-rouge\">ObjectRef</code> 构造函数,这里再把其实现拿出来看看,其实主要是初始化了哪些成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ObjectDef</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">amf3</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">arrayType</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">amf3</span><span class=\"p\">(</span><span class=\"n\">amf3</span><span class=\"p\">),</span>\n <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">dynamic</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">externalizable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">count</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">arrayType</span><span class=\"p\">(</span><span class=\"n\">arrayType</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>可以看到要有一个 amf3,还有 <code class=\"language-plaintext highlighter-rouge\">reset</code> 置为 0,<code class=\"language-plaintext highlighter-rouge\">dynamic</code> 置为 <code class=\"language-plaintext highlighter-rouge\">false</code>,<code class=\"language-plaintext highlighter-rouge\">externalizable</code> 也是 <code class=\"language-plaintext highlighter-rouge\">false</code>,<code class=\"language-plaintext highlighter-rouge\">count</code> 是 0,<code class=\"language-plaintext highlighter-rouge\">arrayType</code> 成员要赋值。</p>\n\n<p>上面是插播哦,下面还要继续哒。创建这么一个对象,注意是 new 出来的,所以我们在《OpenRTMFP/Cumulus Primer(16)AMF解析之AMFReader》一文中提到了 AMFReader 的析构函数中要对 <code class=\"language-plaintext highlighter-rouge\">_objectRef</code> 的每个元素逐一析构的。<code class=\"language-plaintext highlighter-rouge\">arrayType</code> 就设置为 <code class=\"language-plaintext highlighter-rouge\">AMF3_DICTIONARY</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ObjectDef</span><span class=\"o\">*</span> <span class=\"n\">pObjectDef</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nf\">ObjectDef</span><span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">,</span> <span class=\"n\">AMF3_DICTIONARY</span><span class=\"p\">);</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">dynamic</span><span class=\"o\">=</span><span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">pObjectDef</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果标志位是 1,就直接 push back,跟之前一样。不过这里多了一个 <code class=\"language-plaintext highlighter-rouge\">pObjectDef</code>,所以还要设置一下它的计数为 <code class=\"language-plaintext highlighter-rouge\">size</code>,就是 <code class=\"language-plaintext highlighter-rouge\">dictionary</code> 数据大小。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isInline</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_referencing</span><span class=\"p\">)</span>\n <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">reference</span><span class=\"p\">);</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">count</span> <span class=\"o\">=</span> <span class=\"n\">size</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果标志位是 0,就把 <code class=\"language-plaintext highlighter-rouge\">count</code> 设置为下一个变长整数值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_references</span><span class=\"p\">[</span><span class=\"n\">size</span><span class=\"p\">]);</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">count</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">()</span> <span class=\"o\">>></span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">count</span> <span class=\"o\">*=</span> <span class=\"mi\">2</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>读一个字节,如果最小位是 1,weakKeys 就是 true,否则为 false。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">weakKeys</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read8</span><span class=\"p\">()</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n \n <span class=\"k\">return</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 3:CumulusServer 源码主进程主循环分析</title>\n \t<meta name=\"description\" content=\"CumulusServer 主进程的主循环分析,看本文一篇就够了。从绑定地址开始,本文介绍了如何接收数据,如何在 CumulusEdge 和 CumulusServer 的 socket 不同情况下的处理逻辑,如何处理发送方 IP 被禁、数据包大小异常等问题。通过本文让你了解 CumulusServer 的主循环,需要你对 POCO 库有一点了解,还要稍微熟悉 C++ 的基本语法。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 3:CumulusServer 源码主进程主循环分析</h2>\t\t\n\t<time datetime=\"2012-04-15T14:26:58+00:00\" class=\"by-line\">15 Apr 2012, 广州 | 麦克船长 | 总计 3844 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#1绑定地址\" id=\"markdown-toc-1绑定地址\">1、绑定地址</a></li>\n <li><a href=\"#2cumulusserver-接收数据\" id=\"markdown-toc-2cumulusserver-接收数据\">2、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 接收数据</a></li>\n <li><a href=\"#3如果-cumulusedge-端口存在且-edge-socket-可用\" id=\"markdown-toc-3如果-cumulusedge-端口存在且-edge-socket-可用\">3、如果 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 端口存在且 edge socket 可用。</a></li>\n <li><a href=\"#4cumulusserver-和-cumulusedge-的-socket-都没有数据\" id=\"markdown-toc-4cumulusserver-和-cumulusedge-的-socket-都没有数据\">4、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 和 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 都没有数据:</a></li>\n <li><a href=\"#5发送方的-ip-被禁\" id=\"markdown-toc-5发送方的-ip-被禁\">5、发送方的 ip 被禁:</a></li>\n <li><a href=\"#6数据包长度小于可能的最小值12\" id=\"markdown-toc-6数据包长度小于可能的最小值12\">6、数据包长度小于可能的最小值(12)</a></li>\n</ul>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 主进程的主循环分析,看本文一篇就够了。从绑定地址开始,本文介绍了如何接收数据,如何在 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 和 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 socket 不同情况下的处理逻辑,如何处理发送方 IP 被禁、数据包大小异常等问题。通过本文让你了解 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的主循环,需要你对 POCO 库有一点了解,还要稍微熟悉 C++ 的基本语法。</p>\n\n<p>本所要介绍的这个主循环在 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run(const volatile bool& terminate)</code> 函数中。RTMFPServer覆盖 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 的 <code class=\"language-plaintext highlighter-rouge\">run(const volatile bool &terminate)</code> 方法。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"k\">volatile</span> <span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<h3 id=\"1绑定地址\">1、绑定地址</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 IP 地址和端口:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SocketAddress</span> <span class=\"nf\">address</span><span class=\"p\">(</span><span class=\"s\">\"0.0.0.0\"</span><span class=\"p\">,</span><span class=\"n\">_port</span><span class=\"p\">);</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">address</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">);</span>\n<span class=\"err\">绑定</span><span class=\"n\">CumulusEdge</span><span class=\"err\">的</span> <span class=\"n\">IP</span> <span class=\"err\">地址和端口:</span>\n\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SocketAddress</span> <span class=\"nf\">edgesAddress</span><span class=\"p\">(</span><span class=\"s\">\"0.0.0.0\"</span><span class=\"p\">,</span><span class=\"n\">_edgesPort</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">edgesAddress</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>发送者(Client)的 IP 地址和端口:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SocketAddress</span> <span class=\"n\">sender</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">buff</span><span class=\"p\">[</span><span class=\"n\">PACKETRECV_SIZE</span><span class=\"p\">];</span>\n <span class=\"kt\">int</span> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n \n <span class=\"k\">while</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">idle</span> <span class=\"o\">=</span> <span class=\"n\">realTime</span><span class=\"p\">(</span><span class=\"n\">stop</span><span class=\"p\">);</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">stop</span><span class=\"p\">)</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n \n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">isEdges</span><span class=\"o\">=</span><span class=\"nb\">false</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<h3 id=\"2cumulusserver-接收数据\">2、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 接收数据</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 有数据可读:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>从 <code class=\"language-plaintext highlighter-rouge\">socket</code> 读取:把数据存到 <code class=\"language-plaintext highlighter-rouge\">buff</code>,把发送者地址赋给 <code class=\"language-plaintext highlighter-rouge\">sender</code>,把所读长度返回给 <code class=\"language-plaintext highlighter-rouge\">size</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">receiveFrom</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">,</span><span class=\"k\">sizeof</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">),</span><span class=\"n\">sender</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>处理 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 产生的异常:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span><span class=\"s\">\"Main socket reception : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">address</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">);</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"3如果-cumulusedge-端口存在且-edge-socket-可用\">3、如果 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 端口存在且 edge socket 可用。</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 有数据可读:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span> <span class=\"o\">></span> <span class=\"mi\">0</span> <span class=\"o\">&&</span> <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">receiveFrom</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">,</span> <span class=\"k\">sizeof</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">),</span> <span class=\"n\">sender</span><span class=\"p\">);</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">isEdges</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span><span class=\"s\">\"Main socket reception : %s\"</span><span class=\"p\">,</span> <span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">edgesAddress</span><span class=\"p\">,</span> <span class=\"nb\">true</span><span class=\"p\">);</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">Edge</span><span class=\"o\">*</span> <span class=\"n\">pEdge</span> <span class=\"o\">=</span> <span class=\"n\">edges</span><span class=\"p\">(</span><span class=\"n\">sender</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pEdge</span><span class=\"p\">)</span>\n <span class=\"n\">pEdge</span><span class=\"o\">-></span><span class=\"n\">update</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<h3 id=\"4cumulusserver-和-cumulusedge-的-socket-都没有数据\">4、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 和 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 都没有数据:</h3>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 空闲:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">idle</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>主线程等待一秒。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Thread</span><span class=\"o\">::</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_timeLastManage</span><span class=\"p\">.</span><span class=\"n\">isElapsed</span><span class=\"p\">(</span><span class=\"n\">_freqManage</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>Just middle session</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_middle</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Sessions</span><span class=\"o\">::</span><span class=\"n\">Iterator</span> <span class=\"n\">it</span><span class=\"p\">;</span>\n <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"n\">it</span> <span class=\"o\">!=</span> <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span> <span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Middle</span><span class=\"o\">*</span> <span class=\"n\">pMiddle</span> <span class=\"o\">=</span> <span class=\"k\">dynamic_cast</span><span class=\"o\"><</span><span class=\"n\">Middle</span><span class=\"o\">*></span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pMiddle</span><span class=\"p\">)</span>\n <span class=\"n\">pMiddle</span><span class=\"o\">-></span><span class=\"n\">manage</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"p\">}</span>\n <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">_timeLastManage</span><span class=\"p\">.</span><span class=\"n\">update</span><span class=\"p\">();</span>\n <span class=\"n\">manage</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"err\">}</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"5发送方的-ip-被禁\">5、发送方的 ip 被禁:</h3>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isBanned</span><span class=\"p\">(</span><span class=\"n\">sender</span><span class=\"p\">.</span><span class=\"n\">host</span><span class=\"p\">()))</span> <span class=\"p\">{</span>\n <span class=\"n\">INFO</span><span class=\"p\">(</span><span class=\"s\">\"Data rejected because client %s is banned\"</span><span class=\"p\">,</span>\n <span class=\"n\">sender</span><span class=\"p\">.</span><span class=\"n\">host</span><span class=\"p\">().</span><span class=\"n\">toString</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"6数据包长度小于可能的最小值12\">6、数据包长度小于可能的最小值(12)</h3>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">size</span> <span class=\"o\"><</span> <span class=\"n\">RTMFP_MIN_PACKET_SIZE</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Invalid packet\"</span><span class=\"p\">);</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">PacketReader</span> <span class=\"nf\">packet</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Session</span><span class=\"o\">*</span> <span class=\"n\">pSession</span> <span class=\"o\">=</span> <span class=\"n\">findSession</span><span class=\"p\">(</span><span class=\"n\">RTMFP</span><span class=\"o\">::</span><span class=\"n\">Unpack</span><span class=\"p\">(</span><span class=\"n\">packet</span><span class=\"p\">));</span>\n \n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">pSession</span><span class=\"p\">)</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n \n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">pSession</span><span class=\"o\">-></span><span class=\"n\">checked</span><span class=\"p\">)</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">commitCookie</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pSession</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>给 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 或者自己(<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code>)的 <code class=\"language-plaintext highlighter-rouge\">socket</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">pSession</span><span class=\"o\">-></span><span class=\"n\">setEndPoint</span><span class=\"p\">(</span><span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">isEdges</span> <span class=\"o\">?</span> <span class=\"n\">_edgesSocket</span> <span class=\"o\">:</span> <span class=\"n\">_socket</span><span class=\"p\">,</span><span class=\"n\">sender</span><span class=\"p\">);</span>\n <span class=\"n\">pSession</span><span class=\"o\">-></span><span class=\"n\">receive</span><span class=\"p\">(</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"err\">}</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">clear</span><span class=\"p\">();</span>\n <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">clear</span><span class=\"p\">();</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_pCirrus</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">delete</span> <span class=\"n\">_pCirrus</span><span class=\"p\">;</span>\n <span class=\"n\">_pCirrus</span> <span class=\"o\">=</span> <span class=\"nb\">NULL</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 2:CumulusServer 源码启动流程分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的第二篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本文对 CumulusServer 的启动流程进行了详细的源码解读,其中还包括 CumulusServer 如何处理命令行的各个输入选项、各项命令、如何 dump logs、载入配置、处理日志。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 2:CumulusServer 源码启动流程分析</h2>\t\t\n\t<time datetime=\"2012-04-14T11:20:46+00:00\" class=\"by-line\">14 Apr 2012, 广州 | 麦克船长 | 总计 18890 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一cumulus-启动源码分析\" id=\"markdown-toc-一cumulus-启动源码分析\">一、Cumulus 启动源码分析</a> <ul>\n <li><a href=\"#1maincpp-中的-main-函数\" id=\"markdown-toc-1maincpp-中的-main-函数\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数</a></li>\n <li><a href=\"#2maincpp-中的-cumulusserver-构造函数\" id=\"markdown-toc-2maincpp-中的-cumulusserver-构造函数\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer()</code> 构造函数</a></li>\n <li><a href=\"#3maincpp-中的-cumulusserver-的-initialize-成员函数\" id=\"markdown-toc-3maincpp-中的-cumulusserver-的-initialize-成员函数\">3、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 成员函数</a></li>\n <li><a href=\"#4maincpp-中的-cumulusserver-的-main-成员函数\" id=\"markdown-toc-4maincpp-中的-cumulusserver-的-main-成员函数\">4、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 成员函数</a></li>\n <li><a href=\"#5cumulusserver-是-serverapplication-的子类\" id=\"markdown-toc-5cumulusserver-是-serverapplication-的子类\">5、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 是 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的子类</a></li>\n <li><a href=\"#6serverapplication-是-application-的子类\" id=\"markdown-toc-6serverapplication-是-application-的子类\">6、<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 是 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的子类</a></li>\n <li><a href=\"#7反初始化\" id=\"markdown-toc-7反初始化\">7、反初始化</a></li>\n </ul>\n </li>\n <li><a href=\"#二cumulusserver-各项交互功能的源码解读\" id=\"markdown-toc-二cumulusserver-各项交互功能的源码解读\">二、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 各项交互功能的源码解读</a> <ul>\n <li><a href=\"#1命令行选项设定\" id=\"markdown-toc-1命令行选项设定\">1、命令行选项设定</a></li>\n <li><a href=\"#2处理命令行选项\" id=\"markdown-toc-2处理命令行选项\">2、处理命令行选项</a></li>\n <li><a href=\"#6dump-logs\" id=\"markdown-toc-6dump-logs\">6、Dump logs</a></li>\n <li><a href=\"#3停止运行\" id=\"markdown-toc-3停止运行\">3、停止运行</a></li>\n <li><a href=\"#4载入配置\" id=\"markdown-toc-4载入配置\">4、载入配置</a></li>\n <li><a href=\"#5处理日志\" id=\"markdown-toc-5处理日志\">5、处理日志</a></li>\n </ul>\n </li>\n <li><a href=\"#三maincpp-的-main-函数源码分析\" id=\"markdown-toc-三maincpp-的-main-函数源码分析\">三、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数源码分析</a> <ul>\n <li><a href=\"#1maincpp-中的-main-函数中的-server\" id=\"markdown-toc-1maincpp-中的-main-函数中的-server\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数中的 <code class=\"language-plaintext highlighter-rouge\">server</code></a></li>\n <li><a href=\"#2maincpp-中-main-函数的-serverstart\" id=\"markdown-toc-2maincpp-中-main-函数的-serverstart\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数的 <code class=\"language-plaintext highlighter-rouge\">server.start()</code></a></li>\n <li><a href=\"#3回顾一下整个启动流程\" id=\"markdown-toc-3回顾一下整个启动流程\">3、回顾一下整个启动流程</a></li>\n <li><a href=\"#4rtmfpserverprerunstartableprerun-和-rtmfpserverrun-函数源码\" id=\"markdown-toc-4rtmfpserverprerunstartableprerun-和-rtmfpserverrun-函数源码\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer::prerun()</code>、<code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run(...)</code> 函数源码</a></li>\n </ul>\n </li>\n</ul>\n\n<p>本文对 CumulusServer 的启动流程进行了详细的源码解读,其中还包括 CumulusServer 如何处理命令行的各个输入选项、各项命令、如何 dump logs、载入配置、处理日志。首先要知道的是,OpenRTMFP/Cumulus 中使用到的库有 Poco、OpenSSL 和 Lua。</p>\n\n<h3 id=\"一cumulus-启动源码分析\">一、Cumulus 启动源码分析</h3>\n\n<h4 id=\"1maincpp-中的-main-函数\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数</h4>\n\n<p>入口在 <code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">int</span> <span class=\"nf\">main</span><span class=\"p\">(</span><span class=\"kt\">int</span> <span class=\"n\">argc</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">argv</span><span class=\"p\">[])</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先检查内存泄露,不过目前这个开发中的项目还没有实现这个功能,只是个空函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">DetectMemoryLeak</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>然后会创建一个匿名的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 对象,并调用其 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数,该函数由 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 从 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 中继承而来,而 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 由是从 <code class=\"language-plaintext highlighter-rouge\">Application</code> 继承而来的。<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 对象调用 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数,实际是 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数,<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数则是调用 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的函数,而该 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数则是先调用 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数,然后调用 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数,然后调用 <code class=\"language-plaintext highlighter-rouge\">uninitialize()</code> 函数。如果 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数被调用时抛出异常,则不会执行 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数,但仍然会执行 <code class=\"language-plaintext highlighter-rouge\">uninitialize()</code> 函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// Runs the application by performing additional initializations</span>\n <span class=\"c1\">// and calling the main() method.</span>\n <span class=\"k\">return</span> <span class=\"nf\">CumulusServer</span><span class=\"p\">().</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">argc</span><span class=\"p\">,</span> <span class=\"n\">argv</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2maincpp-中的-cumulusserver-构造函数\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer()</code> 构造函数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的构造函数定义为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">CumulusServer</span><span class=\"p\">()</span><span class=\"o\">:</span> <span class=\"n\">_helpRequested</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span> <span class=\"c1\">// 显示帮助信息</span>\n <span class=\"n\">_pCirrus</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">_middle</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">_isInteractive</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">),</span>\n <span class=\"n\">_pLogFile</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3maincpp-中的-cumulusserver-的-initialize-成员函数\">3、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 成员函数</h4>\n\n<p>在执行 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数之前,<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 会启动 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数,传入的参数就是 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 自己,可以猜到 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::Application</code> 的 <code class=\"language-plaintext highlighter-rouge\">run</code> 方法中,调用该函数时的参数是 <code class=\"language-plaintext highlighter-rouge\">this</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">initialize</span><span class=\"p\">(</span><span class=\"n\">Application</span><span class=\"o\">&</span> <span class=\"n\">self</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>调用父函数 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的初始化函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">initialize</span><span class=\"p\">(</span><span class=\"n\">self</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>再继续下面的源码分析之前,先要知道,根据 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::Application</code> ,<code class=\"language-plaintext highlighter-rouge\">Application</code> 类有一些内置的配置属性,如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">application.path</code>: 可执行文件的绝对路径;</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.name</code>: 可执行文件的文件名(含扩展名);</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.baseName</code>: 可执行文件的文件名(不含扩展名)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.dir</code>: 可执行文件的所在目录;</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.configDir</code>: 配置文件所在目录;</li>\n</ul>\n\n<p>所以下面就读取了可执行文件的所在目录,其中第二个参数表示默认值(即当前目录):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">string</span> <span class=\"n\">dir</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.dir\"</span><span class=\"p\">,</span> <span class=\"s\">\"./\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>然后读取配置文件,目录为上一句所得到的 <code class=\"language-plaintext highlighter-rouge\">dir</code>,文件名(不含扩展名)为 <code class=\"language-plaintext highlighter-rouge\">application.basename</code> 内置配置属性值,其默认值为 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code>,然后加上点和扩展名 <code class=\"language-plaintext highlighter-rouge\">.ini</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">loadConfiguration</span><span class=\"p\">(</span><span class=\"n\">dir</span>\n <span class=\"o\">+</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.baseName\"</span><span class=\"p\">,</span> <span class=\"s\">\"CumulusServer\"</span><span class=\"p\">)</span>\n <span class=\"o\">+</span> <span class=\"s\">\".ini\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这样就加载完配置了。然后查看当前的进程是从命令行运行的(命令行是交互的,所以是 interactive),还是以 daemon 方式运行的,这个函数是ServerApplication的一个成员函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_isInteractive</span> <span class=\"o\">=</span> <span class=\"n\">isInteractive</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>然后获取表示日志文件所在目录的字符串,其中 <code class=\"language-plaintext highlighter-rouge\">logs.directory</code> 是外置配置属性(配置文件中),其默认值为上面获取到的可执行文件路径(一般为当前路径)与 <code class=\"language-plaintext highlighter-rouge\">logs</code> 的组合,即一般为当前目录下的 <code class=\"language-plaintext highlighter-rouge\">logs</code> 目录:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">string</span> <span class=\"nf\">logDir</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"logs.directory\"</span><span class=\"p\">,</span> <span class=\"n\">dir</span> <span class=\"o\">+</span> <span class=\"s\">\"logs\"</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<p>创建日志文件目录:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">File</span><span class=\"p\">(</span><span class=\"n\">logDir</span><span class=\"p\">).</span><span class=\"n\">createDirectory</span><span class=\"p\">();</span>\n\n</code></pre></div></div>\n\n<p>日志文件绝对路径,<code class=\"language-plaintext highlighter-rouge\">logs</code> 为默认的日志文件名(不含扩展名的部分),扩展名用0:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_logPath</span> <span class=\"o\">=</span> <span class=\"n\">logDir</span> <span class=\"o\">+</span> <span class=\"s\">\"/\"</span> <span class=\"o\">+</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"logs.name\"</span><span class=\"p\">,</span> <span class=\"s\">\"log\"</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"s\">\".\"</span><span class=\"p\">;</span>\n <span class=\"n\">_pLogFile</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nf\">File</span><span class=\"p\">(</span><span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"s\">\"0\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>用日志流打开日志文件(方式为追加写入):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">open</span><span class=\"p\">(</span><span class=\"n\">_pLogFile</span><span class=\"o\">-></span><span class=\"n\">path</span><span class=\"p\">(),</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">in</span> <span class=\"o\">|</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">ate</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Logs</code> 是一个方法类(其中的 <code class=\"language-plaintext highlighter-rouge\">public</code> 函数都是静态的),<code class=\"language-plaintext highlighter-rouge\">SetLogger</code> 的作用就是将Logs中的似有静态成员设置为某个 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Logger</code> 对象(由于 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 继承了 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Logger</code>)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetLogger</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"4maincpp-中的-cumulusserver-的-main-成员函数\">4、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 成员函数</h4>\n\n<p>OpenRTMFP/Cumulus 是一个基于 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::Application</code> 的服务端应用(准确的说是基于 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::ServerApplication</code> 的服务端应用)。如果没有特殊的启动要求,可以调用 <code class=\"language-plaintext highlighter-rouge\">Poco/Application.h</code> 中定义的宏 <code class=\"language-plaintext highlighter-rouge\">POCO_APP_MAIN</code> 来完成初始化、日志和启动(该宏会根据不同的平台启用不同的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数)。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">run()</code> 函数在调用完 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数后,会调用 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数,该 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数的定义在 <code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">int</span> <span class=\"nf\">main</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">>&</span> <span class=\"n\">args</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>首先看是否是要求帮助信息,<code class=\"language-plaintext highlighter-rouge\">displayHelp</code> 是借助 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::HelpFormatter</code> 实现的,<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的构造函数会在调用时将 <code class=\"language-plaintext highlighter-rouge\">_helpRequested</code> 设置为 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_helpRequested</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">displayHelp</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果不是,则进入启动状态,首先创建一个 <code class=\"language-plaintext highlighter-rouge\">RTMFPServerParams</code> 对象 <code class=\"language-plaintext highlighter-rouge\">params</code>,用来存储 OpenRTMFP/CumulusServer 的基本配置信息。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">RTMFPServerParams</span> <span class=\"n\">params</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">params</code> 存储 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的端口号和 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的端口号:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"port\"</span><span class=\"p\">,</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">port</span><span class=\"p\">);</span>\n <span class=\"n\">UInt16</span> <span class=\"n\">edgesPort</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"edges.port\"</span><span class=\"p\">,</span>\n <span class=\"n\">RTMFP_DEFAULT_PORT</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getBool</span><span class=\"p\">(</span><span class=\"s\">\"edges.activated\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">edgesPort</span><span class=\"o\">==</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"edges.port must have a positive value if \\\n edges.activated is true. Server edges is \\\n disactivated.\"</span><span class=\"p\">);</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesPort</span><span class=\"o\">=</span><span class=\"n\">edgesPort</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">_pCirrus</code> 为 <code class=\"language-plaintext highlighter-rouge\">SocketAddress</code> 的成员,是封装IP地址和端口号的对象。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">pCirrus</span> <span class=\"o\">=</span> <span class=\"n\">_pCirrus</span><span class=\"p\">;</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">middle</span> <span class=\"o\">=</span> <span class=\"n\">_middle</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>UDB 所使用的缓冲区大小:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"udpBufferSize\"</span><span class=\"p\">,</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span>\n <span class=\"s\">\"keepAliveServer\"</span><span class=\"p\">,</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"keepAlivePeer\"</span><span class=\"p\">,</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>失败前 CumulusEdge 的尝试次数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesAttemptsBeforeFallback</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span>\n <span class=\"s\">\"edges.attemptsBeforeFallback\"</span><span class=\"p\">,</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesAttemptsBeforeFallback</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Server</span> <span class=\"nf\">server</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.dir\"</span><span class=\"p\">,</span><span class=\"s\">\"./\"</span><span class=\"p\">),</span>\n <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span><span class=\"n\">config</span><span class=\"p\">());</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">server</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">waitForTerminationRequest()</code> 函数是 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数中必须调用的,意为等待终止运行的操作请求。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// wait for CTRL-C or kill</span>\n <span class=\"n\">waitForTerminationRequest</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>一旦接收到终止操作的请求,就会执行下面这句,用以退出 OpenRTMFP/Cumulus 的运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// Stop the server</span>\n <span class=\"n\">server</span><span class=\"p\">.</span><span class=\"n\">stop</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">catch</code> 一些可能产生的异常:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"Configuration problem : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"CumulusServer : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">what</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(...)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"CumulusServer unknown error\"</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>OpenRTMFP/CumulusServer 停止运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"n\">Application</span><span class=\"o\">::</span><span class=\"n\">EXIT_OK</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"5cumulusserver-是-serverapplication-的子类\">5、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 是 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的子类</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 对其子类有如下要求:</p>\n\n<ul>\n <li>Subsystems must be registered in the constructor.</li>\n <li>All non-trivial initializations must be made in the <code class=\"language-plaintext highlighter-rouge\">initialize()</code>` method.</li>\n <li>At the end of the <code class=\"language-plaintext highlighter-rouge\">main()</code> method, <code class=\"language-plaintext highlighter-rouge\">waitForTerminationRequest()</code> should be called.</li>\n</ul>\n\n<h4 id=\"6serverapplication-是-application-的子类\">6、<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 是 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的子类</h4>\n\n<p>Application 对其子类的要求是,如下这些成员函数必须被覆盖:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">initialize()</code> (the one-argument, protected variant):上一篇已介绍过。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">uninitialize()</code>:下面会介绍,Application 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数会在调用 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数后调用 <code class=\"language-plaintext highlighter-rouge\">uninitialize()</code> 函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">reinitialize()</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">defineOptions()</code>:定义命令行启动选项。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">handleOption()</code>:响应相应的命令行选项。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">main()</code></li>\n</ul>\n\n<h4 id=\"7反初始化\">7、反初始化</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 是继承 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的,<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 是继承 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的。<code class=\"language-plaintext highlighter-rouge\">Application</code> 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数会先调用 <code class=\"language-plaintext highlighter-rouge\">initialize()</code>,然后调用 <code class=\"language-plaintext highlighter-rouge\">main()</code>,最后调用 <code class=\"language-plaintext highlighter-rouge\">uninitialize</code>。最后这个反初始化过程,在 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 就是直接调用父类的 <code class=\"language-plaintext highlighter-rouge\">uninitialize</code> 函数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">uninitialize</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">uninitialize</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"二cumulusserver-各项交互功能的源码解读\">二、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 各项交互功能的源码解读</h3>\n\n<h4 id=\"1命令行选项设定\">1、命令行选项设定</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的命令行选项有:<code class=\"language-plaintext highlighter-rouge\">log(l)</code>、<code class=\"language-plaintext highlighter-rouge\">dump(d)</code>、<code class=\"language-plaintext highlighter-rouge\">cirrus(c)</code>、<code class=\"language-plaintext highlighter-rouge\">middle(m)</code>、<code class=\"language-plaintext highlighter-rouge\">help(h)</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">defineOptions</span><span class=\"p\">(</span><span class=\"n\">OptionSet</span><span class=\"o\">&</span> <span class=\"n\">options</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">defineOptions</span><span class=\"p\">(</span><span class=\"n\">options</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>设定日志级别(0 - 8,默认是 6,表示 info 级别)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"log\"</span><span class=\"p\">,</span> <span class=\"s\">\"l\"</span><span class=\"p\">,</span> <span class=\"s\">\"Log level argument, must be beetween 0 and 8 : \\\n nothing, fatal, critic, error, warn, note, info, debug, trace. \\\n Default value is 6 (info), all logs until info level are displayed.\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">required</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">argument</span><span class=\"p\">(</span><span class=\"s\">\"level\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<p>其他一些选项:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"dump\"</span><span class=\"p\">,</span> <span class=\"s\">\"d\"</span><span class=\"p\">,</span> <span class=\"s\">\"Enables packet traces in logs. Optional arguments \\\n are 'middle' or 'all' respectively to displays just middle packet \\\n process or all packet process. If no argument is given, just outside \\\n packet process will be dumped.\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">,</span><span class=\"s\">\"middle|all\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"cirrus\"</span><span class=\"p\">,</span> <span class=\"s\">\"c\"</span><span class=\"p\">,</span> <span class=\"s\">\"Cirrus address to activate a 'man-in-the-middle' \\\n developer mode in bypassing flash packets to the official cirrus \\\n server of your choice, it's a instable mode to help Cumulus developers, \\\n </span><span class=\"se\">\\\"</span><span class=\"s\">p2p.rtmfp.net:10000</span><span class=\"se\">\\\"</span><span class=\"s\"> for example. By adding the 'dump' argument, \\\n you will able to display Cirrus/Flash packet exchange in your logs \\\n (see 'dump' argument).\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">,</span><span class=\"s\">\"address\"</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"middle\"</span><span class=\"p\">,</span> <span class=\"s\">\"m\"</span><span class=\"p\">,</span><span class=\"s\">\"Enables a 'man-in-the-middle' developer mode \\\n between two peers. It's a instable mode to help Cumulus developers. \\\n By adding the 'dump' argument, you will able to display Flash/Flash \\\n packet exchange in your logs (see 'dump' argument).\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<p>显示帮助信息的选项:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"help\"</span><span class=\"p\">,</span> <span class=\"s\">\"h\"</span><span class=\"p\">,</span> <span class=\"s\">\"Displays help information about command-line usage.\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">required</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">OptionSet</code> 是 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::OptionSet</code>,调用addOption可以向其中增加选项Option。其中required和repeatable表示:</p>\n\n<p>Sets whether the option is required (flag == true) or optional (flag == false).\nReturns true if the option can be specified more than once, or false if at most once.\n当需要显示帮助信息时,调用如下函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">displayHelp</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">HelpFormatter</span> <span class=\"n\">helpFormatter</span><span class=\"p\">(</span><span class=\"n\">options</span><span class=\"p\">());</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">setCommand</span><span class=\"p\">(</span><span class=\"n\">commandName</span><span class=\"p\">());</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">setUsage</span><span class=\"p\">(</span><span class=\"s\">\"OPTIONS\"</span><span class=\"p\">);</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">setHeader</span><span class=\"p\">(</span><span class=\"s\">\"CumulusServer, open source RTMFP server\"</span><span class=\"p\">);</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">cout</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">setCommand()</code>: Sets the command name.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">setUsage()</code>: Sets the usage string.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">setHeader()</code>: Sets the header string.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">format()</code>: Writes the formatted help text to the given stream.</li>\n</ul>\n\n<h4 id=\"2处理命令行选项\">2、处理命令行选项</h4>\n\n<p>参数是选项名和选项值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">handleOption</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">,</span> <span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">handleOption</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">,</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果选项是帮助:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"help\"</span><span class=\"p\">)</span>\n <span class=\"n\">_helpRequested</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是cirrus,即该服务的 IP 和端口号,Poco::URI 中有协议名(Scheme)、IP 地址(Host)、端口号(Port)、查询串(Query)等等。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"cirrus\"</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">URI</span> <span class=\"n\">uri</span><span class=\"p\">(</span><span class=\"s\">\"rtmfp://\"</span><span class=\"o\">+</span><span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"n\">_pCirrus</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"n\">SocketAddress</span><span class=\"p\">(</span><span class=\"n\">uri</span><span class=\"p\">.</span><span class=\"n\">getHost</span><span class=\"p\">(),</span><span class=\"n\">uri</span><span class=\"p\">.</span><span class=\"n\">getPort</span><span class=\"p\">());</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"Mode 'man in the middle' : the exchange will bypass to '%s'\"</span><span class=\"p\">,</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Mode 'man in the middle' error : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">message</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果选项是dump日志:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"dump\"</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">value</span> <span class=\"o\">==</span> <span class=\"s\">\"all\"</span><span class=\"p\">)</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetDump</span><span class=\"p\">(</span><span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">ALL</span><span class=\"p\">);</span>\n <span class=\"k\">else</span> <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">value</span> <span class=\"o\">==</span> <span class=\"s\">\"middle\"</span><span class=\"p\">)</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetDump</span><span class=\"p\">(</span><span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">MIDDLE</span><span class=\"p\">);</span>\n <span class=\"k\">else</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetDump</span><span class=\"p\">(</span><span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">EXTERNAL</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果选项是middle:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"middle\"</span><span class=\"p\">)</span>\n <span class=\"n\">_middle</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果选项是log,表示设定日志级别:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"log\"</span><span class=\"p\">)</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetLevel</span><span class=\"p\">(</span><span class=\"n\">atoi</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">c_str</span><span class=\"p\">()));</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"6dump-logs\">6、Dump logs</h4>\n\n<p>先加一个作用域锁,然后再向日志流写数据。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">dumpHandler</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">data</span><span class=\"p\">,</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ScopedLock</span><span class=\"o\"><</span><span class=\"n\">FastMutex</span><span class=\"o\">></span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_logMutex</span><span class=\"p\">);</span>\n <span class=\"n\">cout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">((</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">((</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">data</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">manageLogFile</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>调用 manageLogFile,主要做一些日志大小超出限制的处理。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">manageLogFile</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先判断是否超过日志文件的大小上线,LOG_SIZE是1000000字节(即约 1 MB)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_pLogFile</span><span class=\"o\">-></span><span class=\"n\">getSize</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"n\">LOG_SIZE</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"kt\">int</span> <span class=\"n\">num</span> <span class=\"o\">=</span> <span class=\"mi\">10</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>打开新日志文件:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">File</span> <span class=\"nf\">file</span><span class=\"p\">(</span><span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"s\">\"10\"</span><span class=\"p\">);</span>\n\n</code></pre></div></div>\n\n<p>如果该文件已经存在,则先删除:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">())</span>\n <span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">remove</span><span class=\"p\">();</span>\n\n <span class=\"k\">while</span> <span class=\"p\">(</span><span class=\"o\">--</span><span class=\"n\">num</span> <span class=\"o\">>=</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">file</span> <span class=\"o\">=</span> <span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"n\">NumberFormatter</span><span class=\"o\">::</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">num</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">())</span>\n <span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">renameTo</span><span class=\"p\">(</span><span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"n\">NumberFormatter</span><span class=\"o\">::</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">num</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">));</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">open</span><span class=\"p\">(</span><span class=\"n\">_pLogFile</span><span class=\"o\">-></span><span class=\"n\">path</span><span class=\"p\">(),</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">in</span> <span class=\"o\">|</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">ate</span><span class=\"p\">);</span>\n <span class=\"err\">}</span> \n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3停止运行\">3、停止运行</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 继承了 <code class=\"language-plaintext highlighter-rouge\">ApplicationKiller</code>,该类中有纯虚函数 <code class=\"language-plaintext highlighter-rouge\">kill()</code> 需要被实现,于是有:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">kill</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">terminate</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">ApplicationKiller</code> 的定义在 <code class=\"language-plaintext highlighter-rouge\">ApplicationKiller.h</code> 中,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">ApplicationKiller</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">ApplicationKiller</span><span class=\"p\">(){}</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">ApplicationKiller</span><span class=\"p\">(){}</span>\n \n <span class=\"k\">virtual</span> <span class=\"kt\">void</span> <span class=\"n\">kill</span><span class=\"p\">()</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"4载入配置\">4、载入配置</h4>\n\n<p>在initialize()函数中调用,上一篇已提到过。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">loadConfiguration</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">path</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">loadConfiguration</span><span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">);</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span><span class=\"p\">(...)</span> <span class=\"p\">{</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"5处理日志\">5、处理日志</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">logHandler</span><span class=\"p\">(</span><span class=\"n\">Thread</span><span class=\"o\">::</span><span class=\"n\">TID</span> <span class=\"n\">threadId</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">threadName</span><span class=\"p\">,</span>\n <span class=\"n\">Priority</span> <span class=\"n\">priority</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"kt\">char</span> <span class=\"o\">*</span><span class=\"n\">filePath</span><span class=\"p\">,</span>\n <span class=\"kt\">long</span> <span class=\"n\">line</span><span class=\"p\">,</span> \n <span class=\"k\">const</span> <span class=\"kt\">char</span> <span class=\"o\">*</span><span class=\"n\">text</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>作用域锁:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ScopedLock</span><span class=\"o\"><</span><span class=\"n\">FastMutex</span><span class=\"o\">></span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_logMutex</span><span class=\"p\">);</span>\n \n <span class=\"n\">Path</span> <span class=\"nf\">path</span><span class=\"p\">(</span><span class=\"n\">filePath</span><span class=\"p\">);</span>\n <span class=\"n\">string</span> <span class=\"n\">file</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">getExtension</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"s\">\"lua\"</span><span class=\"p\">)</span>\n <span class=\"n\">file</span> <span class=\"o\">+=</span> <span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">directory</span><span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">depth</span><span class=\"p\">()</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"s\">\"/\"</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是命令行交互模式(即不是 daemon 模式):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_isInteractive</span><span class=\"p\">)</span>\n <span class=\"n\">printf</span><span class=\"p\">(</span><span class=\"s\">\"%s %s[%ld] %s</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n <span class=\"n\">g_logPriorities</span><span class=\"p\">[</span><span class=\"n\">priority</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">],</span>\n <span class=\"p\">(</span><span class=\"n\">file</span> <span class=\"o\">+</span> <span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">getBaseName</span><span class=\"p\">()).</span><span class=\"n\">c_str</span><span class=\"p\">(),</span>\n <span class=\"n\">line</span><span class=\"p\">,</span>\n <span class=\"n\">text</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>向日志流输出一句日志:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_logStream</span> <span class=\"o\"><<</span> <span class=\"n\">DateTimeFormatter</span><span class=\"o\">::</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">LocalDateTime</span><span class=\"p\">(),</span><span class=\"s\">\"%d/%m %H:%M:%S.%c \"</span><span class=\"p\">)</span>\n <span class=\"o\"><<</span> <span class=\"n\">g_logPriorities</span><span class=\"p\">[</span><span class=\"n\">priority</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> \n <span class=\"o\"><<</span> <span class=\"sc\">'\\t'</span> <span class=\"o\"><<</span> <span class=\"n\">threadName</span> \n <span class=\"o\"><<</span> <span class=\"sc\">'('</span> <span class=\"o\"><<</span> <span class=\"n\">threadId</span> <span class=\"o\"><<</span> <span class=\"s\">\")</span><span class=\"se\">\\t</span><span class=\"s\">\"</span>\n <span class=\"o\"><<</span> <span class=\"p\">(</span><span class=\"n\">file</span> <span class=\"o\">+</span> <span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">getFileName</span><span class=\"p\">())</span> \n <span class=\"o\"><<</span> <span class=\"sc\">'['</span> <span class=\"o\"><<</span> <span class=\"n\">line</span> <span class=\"o\"><<</span> <span class=\"s\">\"] \"</span> \n <span class=\"o\"><<</span> <span class=\"n\">text</span> <span class=\"o\"><<</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">endl</span><span class=\"p\">;</span>\n \n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>日志文件的善后处理(主要处理文件大小限制可能产生的问题):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">manageLogFile</span><span class=\"p\">();</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"三maincpp-的-main-函数源码分析\">三、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数源码分析</h3>\n\n<h4 id=\"1maincpp-中的-main-函数中的-server\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数中的 <code class=\"language-plaintext highlighter-rouge\">server</code></h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中真正启动的是 <code class=\"language-plaintext highlighter-rouge\">server</code>,它继承自 <code class=\"language-plaintext highlighter-rouge\">Cumulus::RTMFPServer</code>,而 <code class=\"language-plaintext highlighter-rouge\">Cumulus::RTMFPServer</code> 又继承自 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Startable</code>、<code class=\"language-plaintext highlighter-rouge\">Cumulus::Gateway</code>、<code class=\"language-plaintext highlighter-rouge\">Cumulus::Handler</code>。而 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Startable</code> 继承自 <code class=\"language-plaintext highlighter-rouge\">Poco::Runnable</code>,所以其是一个可以运行的线程。在 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusServer</code> 中,这是主线程。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Server</span> <span class=\"nf\">server</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.dir\"</span><span class=\"p\">,</span> <span class=\"s\">\"./\"</span><span class=\"p\">),</span> <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span> <span class=\"n\">config</span><span class=\"p\">());</span>\n<span class=\"n\">server</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这是 <code class=\"language-plaintext highlighter-rouge\">CumulusServer/Server.h</code> 中定义的,其构造函数的原型为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Server</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">root</span><span class=\"p\">,</span>\n <span class=\"n\">ApplicationKiller</span><span class=\"o\">&</span> <span class=\"n\">applicationKiller</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">AbstractConfiguration</span><span class=\"o\">&</span> <span class=\"n\">configurations</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>个参数含义如下:</p>\n\n<blockquote>\n <p>The Path Root for the Server Application Killer for Termanting the Server Application Server Configuration</p>\n</blockquote>\n\n<p>距离来说,在我的 Worksapce 中:</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">root</code> 是 <code class=\"language-plaintext highlighter-rouge\">/Users/michael/Development/workspace/eclipse/OpenRTMFP-Cumulus/Debug/</code> 构造函数的初始化列表极长:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Server</span><span class=\"o\">::</span><span class=\"n\">Server</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">root</span><span class=\"p\">,</span>\n <span class=\"n\">ApplicationKiller</span><span class=\"o\">&</span> <span class=\"n\">applicationKiller</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">AbstractConfiguration</span><span class=\"o\">&</span> <span class=\"n\">configurations</span><span class=\"p\">)</span> \n <span class=\"o\">:</span> <span class=\"n\">_blacklist</span><span class=\"p\">(</span><span class=\"n\">root</span> <span class=\"o\">+</span> <span class=\"s\">\"blacklist\"</span><span class=\"p\">,</span> <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">),</span>\n <span class=\"n\">_applicationKiller</span><span class=\"p\">(</span><span class=\"n\">applicationKiller</span><span class=\"p\">),</span>\n <span class=\"n\">_hasOnRealTime</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">),</span>\n <span class=\"n\">_pService</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">luaMail</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"o\">=</span><span class=\"n\">Script</span><span class=\"o\">::</span><span class=\"n\">CreateState</span><span class=\"p\">(),</span>\n <span class=\"n\">configurations</span><span class=\"p\">.</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"smtp.host\"</span><span class=\"p\">,</span><span class=\"s\">\"localhost\"</span><span class=\"p\">),</span>\n <span class=\"n\">configurations</span><span class=\"p\">.</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"smtp.port\"</span><span class=\"p\">,</span><span class=\"n\">SMTPSession</span><span class=\"o\">::</span><span class=\"n\">SMTP_PORT</span><span class=\"p\">),</span>\n <span class=\"n\">configurations</span><span class=\"p\">.</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"smtp.timeout\"</span><span class=\"p\">,</span><span class=\"mi\">60</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>下面调用 <code class=\"language-plaintext highlighter-rouge\">Poco::File</code> 创建目录:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">File</span><span class=\"p\">((</span><span class=\"n\">string</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">WWWPath</span> <span class=\"o\">=</span> <span class=\"n\">root</span> <span class=\"o\">+</span> <span class=\"s\">\"www\"</span><span class=\"p\">).</span><span class=\"n\">createDirectory</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>因为 <code class=\"language-plaintext highlighter-rouge\">roor</code> 是 <code class=\"language-plaintext highlighter-rouge\">/Users/michael/Development/workspace/eclipse/OpenRTMFP-Cumulus/Debug/</code> 目录,所以 <code class=\"language-plaintext highlighter-rouge\">WWWPath</code> 就是 <code class=\"language-plaintext highlighter-rouge\">/Users/michael/Development/workspace/eclipse/OpenRTMFP-Cumulus/Debug/www</code> 目录。然后初始化 <code class=\"language-plaintext highlighter-rouge\">GlobalTable</code>,这个 <code class=\"language-plaintext highlighter-rouge\">GlobalTable</code> 是和 Lua 有关的东东,这里暂不细说,先知道与 Lua 相关就好。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Service</span><span class=\"o\">::</span><span class=\"n\">InitGlobalTable</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>下面就涉及到了 Lua script 了:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SCRIPT_BEGIN</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">)</span>\n <span class=\"n\">SCRIPT_CREATE_PERSISTENT_OBJECT</span><span class=\"p\">(</span><span class=\"n\">Invoker</span><span class=\"p\">,</span><span class=\"n\">LUAInvoker</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span>\n <span class=\"n\">readNextConfig</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">,</span><span class=\"n\">configurations</span><span class=\"p\">,</span><span class=\"s\">\"\"</span><span class=\"p\">);</span>\n <span class=\"n\">lua_setglobal</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">,</span><span class=\"s\">\"cumulus.configs\"</span><span class=\"p\">);</span>\n <span class=\"n\">SCRIPT_END</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>其中 <code class=\"language-plaintext highlighter-rouge\">SCRIPT_BEGIN</code>、<code class=\"language-plaintext highlighter-rouge\">SCRIPT_CREATE_PERSISTENT_OBJECT和SCRIPT_END</code> 都是宏,其定义在 <code class=\"language-plaintext highlighter-rouge\">Script.h</code> 文件中,如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>#define SCRIPT_BEGIN(STATE) \\\n if (lua_State* __pState = STATE) { \\\n const char* __error=NULL;\n \n#define SCRIPT_CREATE_PERSISTENT_OBJECT(TYPE,LUATYPE,OBJ) \\\n Script::WritePersistentObject<TYPE,LUATYPE>(__pState,OBJ); \\\n lua_pop(__pState,1);\n \n#define SCRIPT_END }\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">SCRIPT_BEGIN和SCRIPT_END</code> 经常用到,当与 Lua 相关的操作出现时,都会以这两个宏作为开头和结尾。</p>\n\n<h4 id=\"2maincpp-中-main-函数的-serverstart\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数的 <code class=\"language-plaintext highlighter-rouge\">server.start()</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">RTMFPServerParams</span><span class=\"o\">&</span> <span class=\"n\">params</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>如果 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusServer</code> 正在运行,则返回并终止启动。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">running</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer server is yet running, call stop method before\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设定端口号,如果端口号为 0,则返回并终止启动。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_port</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">port</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_port</span> <span class=\"o\">==</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer port must have a positive value\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设定 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusEdge</code> 的端口号,如果其端口号与 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusSever</code> 端口号相同,则返回并终止启动:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_edgesPort</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesPort</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_port</span> <span class=\"o\">==</span> <span class=\"n\">_edgesPort</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer port must different than RTMFPServer edges.port\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>Cirrus:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_freqManage</span> <span class=\"o\">=</span> <span class=\"mi\">2000000</span><span class=\"p\">;</span> <span class=\"c1\">// 2 sec by default</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">pCirrus</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_pCirrus</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"n\">Target</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">pCirrus</span><span class=\"p\">);</span>\n <span class=\"n\">_freqManage</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span> <span class=\"c1\">// no waiting, direct process in the middle case!</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer started in man-in-the-middle mode with server %s \\\n (unstable debug mode)\"</span><span class=\"p\">,</span> <span class=\"n\">_pCirrus</span><span class=\"o\">-></span><span class=\"n\">address</span><span class=\"p\">.</span><span class=\"n\">toString</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>middle:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_middle</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">middle</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_middle</span><span class=\"p\">)</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer started in man-in-the-middle mode between peers \\\n (unstable debug mode)\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>UDP Buffer:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> \n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span><span class=\"o\">==</span><span class=\"mi\">0</span> <span class=\"o\">?</span> \n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">getReceiveBufferSize</span><span class=\"p\">()</span> <span class=\"o\">:</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span><span class=\"p\">;</span>\n \n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">setReceiveBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">setSendBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">setReceiveBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">setSendBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n \n <span class=\"n\">DEBUG</span><span class=\"p\">(</span><span class=\"s\">\"Socket buffer receving/sending size = %u/%u\"</span><span class=\"p\">,</span>\n <span class=\"n\">udpBufferSize</span><span class=\"p\">,</span>\n <span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n \n <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> \n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span> <span class=\"o\"><</span> <span class=\"mi\">5</span> <span class=\"o\">?</span> <span class=\"mi\">5000</span> <span class=\"o\">:</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> \n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\"><</span> <span class=\"mi\">5</span> <span class=\"o\">?</span> <span class=\"mi\">5000</span> <span class=\"o\">:</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"n\">UInt8</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">edgesAttemptsBeforeFallback</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesAttemptsBeforeFallback</span><span class=\"p\">;</span>\n \n <span class=\"n\">setPriority</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">threadPriority</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>启动线程,进入循环运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">();</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>上句具体的源码实现为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">running</span><span class=\"p\">())</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果在运行则返回并终止启动。然后加一个局部锁。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ScopedLock</span><span class=\"o\"><</span><span class=\"n\">FastMutex</span><span class=\"o\">></span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutex</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果不得不join()到主线程中,那就join()吧</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_haveToJoin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"n\">_haveToJoin</span><span class=\"o\">=</span><span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>然后就运行这个线程吧:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_terminate</span> <span class=\"o\">=</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">);</span>\n <span class=\"n\">_haveToJoin</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3回顾一下整个启动流程\">3、回顾一下整个启动流程</h4>\n\n<p>到此我们先回顾一下启动过程:</p>\n\n<p>从 <code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 的启动入口 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数开始,创建 <code class=\"language-plaintext highlighter-rouge\">Server</code> 对象并启动(调用 <code class=\"language-plaintext highlighter-rouge\">start()</code> 函数)。<code class=\"language-plaintext highlighter-rouge\">Server::start()</code> 中调用其父类(<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code>)的父类(<code class=\"language-plaintext highlighter-rouge\">Startable</code>)的方法 <code class=\"language-plaintext highlighter-rouge\">Startable::start()</code> 开启线程。\n调用 <code class=\"language-plaintext highlighter-rouge\">Startable::start()</code> 函数后,开启线城时传入的参数为 <code class=\"language-plaintext highlighter-rouge\">*this</code>,所以就会运行 <code class=\"language-plaintext highlighter-rouge\">Startable::run()</code>;</p>\n\n<h4 id=\"4rtmfpserverprerunstartableprerun-和-rtmfpserverrun-函数源码\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer::prerun()</code>、<code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run(...)</code> 函数源码</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Startable::run()</code> 调用 <code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 函数,但这个函数被 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 覆盖,所以会运行 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::prerun()</code>,其源码如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">prerun</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFP server starts on %u port\"</span><span class=\"p\">,</span><span class=\"n\">_port</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果CumulusEdge:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFP edges server starts on %u port\"</span><span class=\"p\">,</span><span class=\"n\">_edgesPort</span><span class=\"p\">);</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">onStart</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>运行线程:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">prerun</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>处理异常:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">what</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(...)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer unknown error\"</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果跳出了,则终止运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">onStop</span><span class=\"p\">();</span>\n \n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFP server stops\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>该函数内部又会调用父类的 <code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 函数,该函数调用:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">virtual</span> <span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"k\">volatile</span> <span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>它是一个纯虚函数,由 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 实现。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 会调用 <code class=\"language-plaintext highlighter-rouge\">void run(const volatile bool& terminate)</code> 方法,该方法被 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 覆盖了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">prerun</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">_terminate</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"o\">!</span><span class=\"n\">_terminate</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 覆盖 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 的 <code class=\"language-plaintext highlighter-rouge\">run(const volatile bool &terminate)</code> 方法。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"k\">volatile</span> <span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">...</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 1:入门介绍、部署与 Hello World</title>\n \t<meta name=\"description\" content=\"RTMFP 是 Adobe 开发的基于 UDP 协议的实时传输媒体流协议,支持 P2P 传输,具有较高的实时性和安全性。它的主要应用场景是视频通信、语音通信和网络游戏。OpenRTMFP 是一个开源的 RTMFP 实现,可以用于构建基于 RTMFP 的应用程序。Cumulus 是一个基于 OpenRTMFP 的服务器,提供 RTMFP 服务。它具有轻量级、跨平台和可扩展的特点,并且还提供了负载均衡和可扩展性解决方案。YY 语音的 Web 端音视频流媒体能力,正是基于 RTMFP 协议做的迭代优化实现的。本文是船长关于这个系列文章的第一篇。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 1:入门介绍、部署与 Hello World</h2>\t\t\n\t<time datetime=\"2012-04-09T18:57:19+00:00\" class=\"by-line\">09 Apr 2012, 广州 | 麦克船长 | 总计 8401 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一rtmfp是什么\" id=\"markdown-toc-一rtmfp是什么\">一、RTMFP 是什么?</a> <ul>\n <li><a href=\"#文件分享-p2p-和实时流媒体-p2p-的区别是什么\" id=\"markdown-toc-文件分享-p2p-和实时流媒体-p2p-的区别是什么\">文件分享 P2P 和实时流媒体 P2P 的区别是什么?</a></li>\n <li><a href=\"#rtmfp-和-rtmp-之间的区别是什么\" id=\"markdown-toc-rtmfp-和-rtmp-之间的区别是什么\">RTMFP 和 RTMP 之间的区别是什么?</a></li>\n <li><a href=\"#flash-player-支持-rtmfp-吗\" id=\"markdown-toc-flash-player-支持-rtmfp-吗\">Flash Player 支持 RTMFP 吗?</a></li>\n <li><a href=\"#cumulus-使用-adobe-的-cirrus-key-吗\" id=\"markdown-toc-cumulus-使用-adobe-的-cirrus-key-吗\">Cumulus 使用 Adobe 的 Cirrus Key 吗?</a></li>\n <li><a href=\"#这个开源项目合法吗\" id=\"markdown-toc-这个开源项目合法吗\">这个开源项目合法吗?</a></li>\n </ul>\n </li>\n <li><a href=\"#二openrtmfp和cumulus\" id=\"markdown-toc-二openrtmfp和cumulus\">二、OpenRTMFP 和 Cumulus</a></li>\n <li><a href=\"#三入门介绍与部署cumulusserver\" id=\"markdown-toc-三入门介绍与部署cumulusserver\">三、入门介绍与部署 CumulusServer</a> <ul>\n <li><a href=\"#1背景介绍\" id=\"markdown-toc-1背景介绍\">1、背景介绍</a></li>\n <li><a href=\"#2准备工作\" id=\"markdown-toc-2准备工作\">2、准备工作</a></li>\n <li><a href=\"#3安装\" id=\"markdown-toc-3安装\">3、安装</a> <ul>\n <li><a href=\"#31外部依赖的安装\" id=\"markdown-toc-31外部依赖的安装\">3.1、外部依赖的安装</a></li>\n <li><a href=\"#32安装openrtmfpcumulus\" id=\"markdown-toc-32安装openrtmfpcumulus\">3.2、安装 OpenRTMFP/Cumulus</a></li>\n </ul>\n </li>\n <li><a href=\"#4配置\" id=\"markdown-toc-4配置\">4、配置</a></li>\n <li><a href=\"#5启动\" id=\"markdown-toc-5启动\">5、启动</a></li>\n <li><a href=\"#6基本使用\" id=\"markdown-toc-6基本使用\">6、基本使用</a></li>\n <li><a href=\"#7扩展cumulusserverserverapplication\" id=\"markdown-toc-7扩展cumulusserverserverapplication\">7、扩展 CumulusServer(Server Application)</a></li>\n </ul>\n </li>\n <li><a href=\"#三用lua编写helloworld应用扩展cumulusserver\" id=\"markdown-toc-三用lua编写helloworld应用扩展cumulusserver\">三、用 Lua 编写 HelloWorld 应用扩展 CumulusServer</a> <ul>\n <li><a href=\"#1server-side\" id=\"markdown-toc-1server-side\">1、Server-side</a> <ul>\n <li><a href=\"#11serverconfiguration\" id=\"markdown-toc-11serverconfiguration\">1.1、Server configuration</a></li>\n <li><a href=\"#12applicationfile\" id=\"markdown-toc-12applicationfile\">1.2、Application file</a></li>\n </ul>\n </li>\n <li><a href=\"#2client-side\" id=\"markdown-toc-2client-side\">2、Client-side</a></li>\n <li><a href=\"#3运行结果\" id=\"markdown-toc-3运行结果\">3、运行结果</a></li>\n <li><a href=\"#4远程测试一个免费的测试服务器\" id=\"markdown-toc-4远程测试一个免费的测试服务器\">4、远程测试:一个免费的测试服务器</a></li>\n </ul>\n </li>\n</ul>\n\n<h3 id=\"一rtmfp是什么\">一、RTMFP 是什么?</h3>\n\n<p>Real-Time Media Flow Protocol(RTMFP)是 Adobe 开发的一种基于 UDP 并支持 P2P 的实时传输媒体流。主要特点是:高传输效率(可以使用压缩和算法来优化流量从而提高传输效率)、高实时性(可以保证媒体流的实时性使得视频通信和其他实时通信更加流畅)、支持 P2P 传输(减少对服务器的依赖从而减少带宽和服务器资源消耗)、高安全性(加密媒体流从而保证其安全性)。</p>\n\n<p>RTMFP 的主要应用场景包括:视频通信(视频聊天和视频会议)、语音通信(语音聊天、电话)、网络游戏。不过 RTMFP 目前仅有 Adobe 开发的版本,所以它并不是个开源项目,而是个商业化服务。那么有没有开源版本呢?</p>\n\n<h4 id=\"文件分享-p2p-和实时流媒体-p2p-的区别是什么\">文件分享 P2P 和实时流媒体 P2P 的区别是什么?</h4>\n\n<p>RTMFP 是一个 P2P 系统,但它仅针对实时通信时直接用户到用户之间的通信而设计,不能用于多个对等方之间进行文件共享(使用分段下载)。Facebook 在其 Pipe 应用中使用此协议将大文件直接在两个用户之间传输。</p>\n\n<h4 id=\"rtmfp-和-rtmp-之间的区别是什么\">RTMFP 和 RTMP 之间的区别是什么?</h4>\n\n<p>RTMP 是实时消息协议,RTMFP 代表实时媒体流协议。RTMFP 基于用户数据报协议(UDP),而 RTMP 基于传输控制协议(TCP)。\n与 RTMP 不同,RTMFP 还支持直接从一个 Adobe Flash Player 传输数据到另一个,而无需经过服务器。</p>\n\n<h4 id=\"flash-player-支持-rtmfp-吗\">Flash Player 支持 RTMFP 吗?</h4>\n\n<p>RTMFP 是基于用户数据报协议(UDP)的,而 RTMP 是基于传输控制协议(TCP)的。与 RTMP 不同,RTMFP 还支持直接在两个 Adobe Flash Player 之间发送数据,而不经过服务器。Flash Player 10.0 仅允许一对一通信进行 P2P,但从 10.1 开始允许应用程序级别的多播。Flash Player 查找适当的分发路由(覆盖网络),并可以将其分发到通过 P2P 连接的组。</p>\n\n<h4 id=\"cumulus-使用-adobe-的-cirrus-key-吗\">Cumulus 使用 Adobe 的 Cirrus Key 吗?</h4>\n\n<p>不!当然,这是Cumulus的主要目标:成为Cirrus GPL的替代品。唯一的限制是:你的CPU,内存和单台机器的端口数。</p>\n\n<h4 id=\"这个开源项目合法吗\">这个开源项目合法吗?</h4>\n\n<p>在美国,数字千年版权法(Digital Millennium Copyright Act)规定,逆向工程用于协议互操作性是合法的。你可以在 WikiPedia 上查看相关讨论:</p>\n\n<ul>\n <li>http://en.wikipedia.org/wiki/Real_Time_Media_Flow_Protocol</li>\n <li>http://en.wikipedia.org/wiki/Proprietary_protocol</li>\n <li>http://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act</li>\n</ul>\n\n<p>当逆向工程的目的是协议互操作性时,有法律先例。在美国,数字千年版权法(Digital Millennium Copyright Act)为逆向工程软件以使其与其他软件互操作提供了安全保障。</p>\n\n<h3 id=\"二openrtmfp和cumulus\">二、OpenRTMFP 和 Cumulus</h3>\n\n<p>OpenRTMFP 是一个开源的 RTMFP 实现,可以用于构建基于 RTMFP 的应用程序。它包含了 RTMFP 协议的实现,以及一些额外的功能,如媒体流传输、P2P 通信、脚本引擎和数据存储。</p>\n\n<p>Cumulus 是一个基于 OpenRTMFP 的服务器,是一个完整的开源且跨平台的 RTMFP 服务器,可通过脚本进行扩展。CumulusServer 根据 GPL 许可在考虑以下 4 个概念的情况下开发:速度、轻量、跨平台和可扩展。尽管尚未发布版本,但只有在 CumulusServer 经过测试和批准后才会将代码推送到 github。实际上,主要稳定的功能有:</p>\n\n<ul>\n <li>P2P rendez-vous service</li>\n <li>live streaming</li>\n <li>RPC、pull、push exchange,实际上客户端和服务器之间的所有 AMF 可能交换</li>\n <li>脚本引擎,用于创建自己的应用服务器或扩展 Cumulus 的功能</li>\n <li>可扩展性和负载均衡解决方案</li>\n</ul>\n\n<p>下面的内容是本篇 blog 的重点,包括两部分:先是 OpenRTMFP 应用的核心 CumulusServer 的入门介绍与部署,然后用 Lua 编写 HelloWorld 应用扩展 CumulusServer,我们开始吧!</p>\n\n<h3 id=\"三入门介绍与部署cumulusserver\">三、入门介绍与部署 CumulusServer</h3>\n\n<h4 id=\"1背景介绍\">1、背景介绍</h4>\n\n<p>OpenRTMFP 可以帮助你实现 Flash 的实时应用的高并发扩展,OpenRTMFP/Cumulus 是基于 GNU General Public License 的。</p>\n\n<p>POCO:POrtable COmponents,是一个强大的开源 C++ 库。其在 C++ 开发中的角色,相当于 Java Class Library、苹果的 Cocoa、.NET framework。</p>\n\n<h4 id=\"2准备工作\">2、准备工作</h4>\n\n<p>下载:</p>\n\n<table>\n <thead>\n <tr>\n <th><strong>External Dependencies</strong></th>\n <th><strong>Official Site</strong></th>\n <th><strong>Windows</strong></th>\n <th><strong>Linux/OSX</strong></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>OpenSSL</td>\n <td><a href=\"http://www.slproweb.com/products/Win32OpenSSL.html\">Official Site</a></td>\n <td><a href=\"http://www.slproweb.com/download/Win32OpenSSL_Light-1_0_1.exe\">Download</a></td>\n <td><a href=\"http://www.openssl.org/source/openssl-1.0.1.tar.gz\">Download</a></td>\n </tr>\n <tr>\n <td>Lua</td>\n <td><a href=\"http://www.lua.org/\">Official Site</a></td>\n <td><a href=\"http://luaforwindows.googlecode.com/files/LuaForWindows_v5.1.4-45.exe\">Download</a></td>\n <td><a href=\"http://www.lua.org/ftp/lua-5.1.5.tar.gz\">Download</a></td>\n </tr>\n <tr>\n <td>POCO</td>\n <td><a href=\"http://pocoproject.org/\">Official Site</a></td>\n <td><a href=\"http://downloads.sourceforge.net/project/poco/sources/poco-1.4.3/poco-1.4.3p1.zip\">Download</a></td>\n <td><a href=\"https://sourceforge.net/projects/poco/files/sources/poco-1.4.3/poco-1.4.3p1.tar.gz/download\">Download</a></td>\n </tr>\n </tbody>\n</table>\n\n<p>注意:POCO for linux 版本必须是 1.4.0 或更高,否则会引起 TCP 相关的 bug。</p>\n\n<h4 id=\"3安装\">3、安装</h4>\n\n<h5 id=\"31外部依赖的安装\">3.1、外部依赖的安装</h5>\n\n<p>Windows 下略,Linux 下基本就是:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span>./configuremakesudo\n<span class=\"nv\">$ </span>make <span class=\"nb\">install</span>\n</code></pre></div></div>\n\n<h5 id=\"32安装openrtmfpcumulus\">3.2、安装 OpenRTMFP/Cumulus</h5>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">cd </span>OpenRTMFP-Cumulus/CumulusLib\n<span class=\"nv\">$ </span>make\n<span class=\"nv\">$ </span><span class=\"nb\">cd</span> ../CumulusServer\n<span class=\"nv\">$ </span>make\n</code></pre></div></div>\n\n<p>如果出现了 <code class=\"language-plaintext highlighter-rouge\">.h</code> 文件、lib 库找不到的情况,请修改 OpenRTMFP-Cumulus/CumulusLib/Makefile 或 OpenRTMFP-Cumulus/CumulusServer/Makefile。</p>\n\n<h4 id=\"4配置\">4、配置</h4>\n\n<p>通过编写 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP-Cumulus/CumulusServer/CumulusServer.ini</code> 文件来为 OpenRTMFP-Cumulus 进行个性化配置(默认是没有这个文件的),这个文件的内容形如:</p>\n\n<div class=\"language-lua highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">;</span><span class=\"n\">CumulusServer</span><span class=\"p\">.</span><span class=\"n\">ini</span>\n<span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"mi\">1985</span>\n<span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> <span class=\"mi\">114688</span>\n<span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> <span class=\"mi\">15</span>\n<span class=\"p\">[</span><span class=\"n\">logs</span><span class=\"p\">]</span>\n<span class=\"n\">name</span><span class=\"o\">=</span><span class=\"n\">log</span>\n<span class=\"n\">directory</span><span class=\"o\">=</span><span class=\"n\">C</span><span class=\"p\">:</span><span class=\"o\">/</span><span class=\"n\">CumulusServer</span><span class=\"o\">/</span><span class=\"n\">logs</span>\n</code></pre></div></div>\n\n<p>一些字段的设置含义如下,摘自:<a href=\"https://github.com/OpenRTMFP/Cumulus/wiki/Installation\">地址</a>。</p>\n\n<ul>\n <li>公开给 Client 的端口号 <code class=\"language-plaintext highlighter-rouge\">port</code>,默认值是 1935(RTMFP 服务器的默认端口),用于 CumulusServer 监听 RTMFP 请求。</li>\n <li>UDP 缓冲区字节数 <code class=\"language-plaintext highlighter-rouge\">udpBufferSize</code>, allows to change the size in bytes of UDP reception and sending buffer. Increases this value if your operating system has a default value too lower for important loads.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">keepAliveServer</code>, time in seconds for periodically sending packets keep-alive with server, 15s by default (valid value is from 5s to 255s).</li>\n <li><code class=\"language-plaintext highlighter-rouge\">keepAlivePeer</code>, time in seconds for periodically sending packets keep-alive between peers, 10s by default (valid value is from 5s to 255s).</li>\n <li><code class=\"language-plaintext highlighter-rouge\">edges.activated</code>, activate or not the edges server on the RTMFP server (see CumulusEdge, Scalability page for more details about CumulusEdge). By default, CumulusServer stays a RTMFP server without edges ability (default value is false).</li>\n <li><code class=\"language-plaintext highlighter-rouge\">edges.port</code>, port for the edges server, to accept incoming new CumulusEdge instances (see CumulusEdge, Scalability page for more details about CumulusEdge). By default, it’s the port 1936.</li>\n</ul>\n\n<blockquote>\n <p>Warning: This port will receive plain text request from edges, for this purpose it should not be made public. It’s very important for security consideration. It must be available only for CumulusEdge instances, and anything else.</p>\n</blockquote>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">edges.attemptsBeforeFallback</code>, number of CumulusEdge attempt connections before falling back to CumulusServer (see CumulusEdge, Scalability page for more details about CumulusEdge). By default the value is 2 (in practical, 2 attempts happens after 5 sec approximately).</li>\n <li>SMTP IP 地址 <code class=\"language-plaintext highlighter-rouge\">smtp.host</code>, configure a SMTP host to use mails feature provided by Cumulus in server application (see Server Application, Sockets page for more details about mails feature). By default the value is localhost.</li>\n <li>SMTP 端口 <code class=\"language-plaintext highlighter-rouge\">smtp.port</code>, configure a SMTP port to use mails feature provided by Cumulus in server application (see Server Application, Sockets page for more details about mails feature). By default the value is 25.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">smtp.timeout</code>, configure a SMTP timeout session in seconds to use mails feature provided by Cumulus in server application (see Server Application, Sockets page for more details about mails feature). By default the value is 60 seconds.</li>\n <li>日志路径 <code class=\"language-plaintext highlighter-rouge\">logs.directory</code>,默认是 <code class=\"language-plaintext highlighter-rouge\">CumulusServer/logsby</code>。</li>\n <li>日志文件名称 <code class=\"language-plaintext highlighter-rouge\">logs.name</code>,默认是<code class=\"language-plaintext highlighter-rouge\">log</code>。</li>\n</ul>\n\n<h4 id=\"5启动\">5、启动</h4>\n\n<p>Windows 下的启动方法为:</p>\n\n<pre><code class=\"language-dos\">$ CumulusServer.exe /registerService [/displayName=CumulusServer /description=\"Open Source RTMFP Server\" /startup=automatic]\n</code></pre>\n\n<p>Unix-like 下的启动方法为:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">sudo</span> ./CumulusServer <span class=\"nt\">--daemon</span> <span class=\"o\">[</span><span class=\"nt\">--pidfile</span><span class=\"o\">=</span>/var/run/CumulusServer.pid]\n</code></pre></div></div>\n\n<p>具体地,我的启动命令为:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">sudo</span> ./CumulusServer <span class=\"nt\">--daemon</span> <span class=\"nt\">--pidfile</span><span class=\"o\">=</span>./CumulusServer.pid\n</code></pre></div></div>\n\n<h4 id=\"6基本使用\">6、基本使用</h4>\n\n<p>本地 Flash client 可以通过如下语句连接:</p>\n\n<div class=\"language-as highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">$</span> <span class=\"kd\">var</span> <span class=\"nx\">nc</span><span class=\"o\">:</span><span class=\"nx\">NetConnection</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nx\">NetConnection</span><span class=\"p\">()</span><span class=\"o\">;</span><span class=\"nx\">nc</span><span class=\"p\">.</span><span class=\"nx\">connect</span><span class=\"p\">(</span><span class=\"s2\">\"rtmfp://localhost/\"</span><span class=\"p\">)</span><span class=\"o\">;</span>\n</code></pre></div></div>\n\n<p>RTMFP默认是采用1935端口,如果你特别指定了其他端口,比如12345,请使用如下方式:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>nc.connect(\"rtmfp://localhost:12345/\");\n</code></pre></div></div>\n\n<h4 id=\"7扩展cumulusserverserverapplication\">7、扩展 CumulusServer(Server Application)</h4>\n\n<p>启动CumulusServer后,会在可执行文件的目录下出现一个www目录,该目录的作用,就是作为 Server Application 的默认根目录。具体的对应关系如下:</p>\n\n<blockquote>\n <p>rtmfp://host:port/ -> [CumulusServer folder]/www/main.lua (root application)</p>\n</blockquote>\n\n<blockquote>\n <p>rtmfp://host:port/myApplication -> [CumulusServer folder]/www/myApplication/main.lua</p>\n</blockquote>\n\n<blockquote>\n <p>rtmfp://host:port/Games/myGame -> [CumulusServer folder]/www/Games/myGame/main.lua</p>\n</blockquote>\n\n<p>另外要提醒的是,如果main.lua文件被修改,则不需要重启 CumulusServer,因为 Server Application 的创建是一种动态的方式。</p>\n\n<h3 id=\"三用lua编写helloworld应用扩展cumulusserver\">三、用 Lua 编写 HelloWorld 应用扩展 CumulusServer</h3>\n\n<p>下面的这个实例是在本地(Client 与 Server 位于同一机器上)测试的。</p>\n\n<h4 id=\"1server-side\">1、Server-side</h4>\n\n<h5 id=\"11serverconfiguration\">1.1、Server configuration</h5>\n\n<div class=\"language-lua highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">;</span> <span class=\"n\">CumulusServer</span><span class=\"p\">.</span><span class=\"n\">ini</span>\n<span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"mi\">1935</span>\n<span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> <span class=\"mi\">114688</span>\n<span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> <span class=\"mi\">15</span>\n<span class=\"p\">[</span><span class=\"n\">logs</span><span class=\"p\">]</span><span class=\"n\">name</span> <span class=\"o\">=</span> <span class=\"n\">log</span>\n<span class=\"n\">directory</span> <span class=\"o\">=</span> <span class=\"n\">logs</span>\n</code></pre></div></div>\n\n<h5 id=\"12applicationfile\">1.2、Application file</h5>\n\n<div class=\"language-lua highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">function</span> <span class=\"nf\">onConnection</span><span class=\"p\">(</span><span class=\"n\">client</span><span class=\"p\">,</span><span class=\"n\">response</span><span class=\"p\">,</span><span class=\"o\">...</span><span class=\"p\">)</span>\n <span class=\"k\">function</span> <span class=\"nf\">client</span><span class=\"p\">:</span><span class=\"n\">test</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">)</span>\n <span class=\"n\">name</span><span class=\"p\">,</span><span class=\"n\">firstname</span> <span class=\"o\">=</span> <span class=\"n\">unpack</span><span class=\"p\">(</span><span class=\"n\">arg</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"s2\">\"Hello \"</span><span class=\"o\">..</span><span class=\"n\">firstname</span><span class=\"o\">..</span><span class=\"s2\">\" \"</span><span class=\"o\">..</span><span class=\"n\">name</span>\n <span class=\"k\">end</span>\n <span class=\"k\">end</span>\n</code></pre></div></div>\n\n<h4 id=\"2client-side\">2、Client-side</h4>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">// CumulusClient.as</span>\n\n<span class=\"kn\">package</span> <span class=\"err\">{</span>\n <span class=\"nn\">import</span> <span class=\"n\">flash</span><span class=\"o\">.</span><span class=\"na\">display</span><span class=\"o\">.</span><span class=\"na\">Sprite</span><span class=\"o\">;</span>\n <span class=\"kn\">import</span> <span class=\"nn\">flash.net.NetConnection</span><span class=\"o\">;</span>\n <span class=\"kn\">import</span> <span class=\"nn\">flash.net.NetStream</span><span class=\"o\">;</span>\n <span class=\"kn\">import</span> <span class=\"nn\">flash.net.Responder</span><span class=\"o\">;</span>\n\n <span class=\"kd\">public</span> <span class=\"kd\">class</span> <span class=\"nc\">CumulusClient</span> <span class=\"kd\">extends</span> <span class=\"nc\">Sprite</span> <span class=\"o\">{</span>\n <span class=\"kd\">private</span> <span class=\"kt\">var</span> <span class=\"nl\">nc:</span><span class=\"nc\">NetConnection</span> <span class=\"o\">=</span> <span class=\"kc\">null</span><span class=\"o\">;</span>\n \t<span class=\"kd\">private</span> <span class=\"kt\">var</span> <span class=\"nl\">ns:</span><span class=\"nc\">NetStream</span> <span class=\"o\">=</span> <span class=\"kc\">null</span><span class=\"o\">;</span>\n \t\n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">CumulusClient</span><span class=\"o\">()</span> <span class=\"o\">{</span>\n <span class=\"n\">nc</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">NetConnection</span><span class=\"o\">();</span>\n <span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">connect</span><span class=\"o\">(</span><span class=\"s\">\"rtmfp://localhost\"</span><span class=\"o\">);</span>\n <span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">client</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"o\">;</span>\n <span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">call</span><span class=\"o\">(</span><span class=\"s\">\"test\"</span><span class=\"o\">,</span><span class=\"k\">new</span> <span class=\"nc\">Responder</span><span class=\"o\">(</span><span class=\"n\">onResult</span><span class=\"o\">,</span><span class=\"n\">onStatus</span><span class=\"o\">),</span> <span class=\"s\">\"OpenRTMFP/Cumulus\"</span><span class=\"o\">,</span> <span class=\"s\">\"World\"</span><span class=\"o\">)</span>\n <span class=\"o\">}</span>\n \n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">close</span><span class=\"o\">():</span><span class=\"kt\">void</span> <span class=\"o\">{</span> \n\t\t\t<span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">close</span><span class=\"o\">();</span>\n \t<span class=\"o\">}</span>\n \n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">onStatus</span><span class=\"o\">(</span><span class=\"nl\">status:</span><span class=\"nc\">Object</span><span class=\"o\">):</span><span class=\"kt\">void</span> <span class=\"o\">{</span>\n \t<span class=\"n\">trace</span><span class=\"o\">(</span><span class=\"n\">status</span><span class=\"o\">.</span><span class=\"na\">description</span><span class=\"o\">)</span>\n\t <span class=\"o\">}</span>\n \n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">onResult</span><span class=\"o\">(</span><span class=\"nl\">response:</span><span class=\"nc\">Object</span><span class=\"o\">):</span><span class=\"kt\">void</span> <span class=\"o\">{</span>\n \t<span class=\"n\">trace</span><span class=\"o\">(</span><span class=\"n\">response</span><span class=\"o\">)</span> <span class=\"c1\">// expected to display \"Hello World OpenRTMFP/Cumulus\" </span>\n\t <span class=\"o\">}</span> \n\t<span class=\"o\">}</span>\n<span class=\"o\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3运行结果\">3、运行结果</h4>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Hello World OpenRTMFP/Cumulus\n[SWF] CumulusClient.swf - 解压缩后为 1,776 个字节\n[卸装 SWF] CumulusClient.swf\n</code></pre></div></div>\n\n<h4 id=\"4远程测试一个免费的测试服务器\">4、远程测试:一个免费的测试服务器</h4>\n\n<p>获取 Developer Key 的地址:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>http://108.59.252.39:8080/CumulusServer/index.jsp\n</code></pre></div></div>\n\n<p>服务器配置信息:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Server: amd64 OS: Linux 2.6.18-028stab095.1\nServer IP: 108.59.252.39\nOpenRTMFP as of: 22.Feb.2012\n</code></pre></div></div>\n\n<p>编写服务器段应用地址:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>http://108.59.252.39:8080/CumulusServer/manage_ssls.jsp\n</code></pre></div></div>\n\n<p>快去试试吧 :)</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"static_files":[{"basename":"KaTeX_AMS-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_AMS-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_AMS-Regular.ttf"},{"basename":"KaTeX_AMS-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_AMS-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_AMS-Regular.woff"},{"basename":"KaTeX_AMS-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_AMS-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_AMS-Regular.woff2"},{"basename":"KaTeX_Caligraphic-Bold","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Caligraphic-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Bold.ttf"},{"basename":"KaTeX_Caligraphic-Bold","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Caligraphic-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Bold.woff"},{"basename":"KaTeX_Caligraphic-Bold","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Caligraphic-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Bold.woff2"},{"basename":"KaTeX_Caligraphic-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Caligraphic-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Regular.ttf"},{"basename":"KaTeX_Caligraphic-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Caligraphic-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Regular.woff"},{"basename":"KaTeX_Caligraphic-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Caligraphic-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Regular.woff2"},{"basename":"KaTeX_Fraktur-Bold","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Fraktur-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Bold.ttf"},{"basename":"KaTeX_Fraktur-Bold","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Fraktur-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Bold.woff"},{"basename":"KaTeX_Fraktur-Bold","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Fraktur-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Bold.woff2"},{"basename":"KaTeX_Fraktur-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Fraktur-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Regular.ttf"},{"basename":"KaTeX_Fraktur-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Fraktur-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Regular.woff"},{"basename":"KaTeX_Fraktur-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Fraktur-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Regular.woff2"},{"basename":"KaTeX_Main-Bold","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Bold.ttf"},{"basename":"KaTeX_Main-Bold","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Bold.woff"},{"basename":"KaTeX_Main-Bold","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Bold.woff2"},{"basename":"KaTeX_Main-BoldItalic","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-BoldItalic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-BoldItalic.ttf"},{"basename":"KaTeX_Main-BoldItalic","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-BoldItalic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-BoldItalic.woff"},{"basename":"KaTeX_Main-BoldItalic","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-BoldItalic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-BoldItalic.woff2"},{"basename":"KaTeX_Main-Italic","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Italic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Italic.ttf"},{"basename":"KaTeX_Main-Italic","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Italic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Italic.woff"},{"basename":"KaTeX_Main-Italic","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Italic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Italic.woff2"},{"basename":"KaTeX_Main-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Regular.ttf"},{"basename":"KaTeX_Main-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Regular.woff"},{"basename":"KaTeX_Main-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Main-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Regular.woff2"},{"basename":"KaTeX_Math-BoldItalic","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Math-BoldItalic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-BoldItalic.ttf"},{"basename":"KaTeX_Math-BoldItalic","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Math-BoldItalic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-BoldItalic.woff"},{"basename":"KaTeX_Math-BoldItalic","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Math-BoldItalic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-BoldItalic.woff2"},{"basename":"KaTeX_Math-Italic","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Math-Italic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-Italic.ttf"},{"basename":"KaTeX_Math-Italic","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Math-Italic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-Italic.woff"},{"basename":"KaTeX_Math-Italic","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Math-Italic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-Italic.woff2"},{"basename":"KaTeX_SansSerif-Bold","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Bold.ttf"},{"basename":"KaTeX_SansSerif-Bold","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Bold.woff"},{"basename":"KaTeX_SansSerif-Bold","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Bold.woff2"},{"basename":"KaTeX_SansSerif-Italic","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Italic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Italic.ttf"},{"basename":"KaTeX_SansSerif-Italic","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Italic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Italic.woff"},{"basename":"KaTeX_SansSerif-Italic","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Italic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Italic.woff2"},{"basename":"KaTeX_SansSerif-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Regular.ttf"},{"basename":"KaTeX_SansSerif-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Regular.woff"},{"basename":"KaTeX_SansSerif-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_SansSerif-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Regular.woff2"},{"basename":"KaTeX_Script-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Script-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Script-Regular.ttf"},{"basename":"KaTeX_Script-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Script-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Script-Regular.woff"},{"basename":"KaTeX_Script-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Script-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Script-Regular.woff2"},{"basename":"KaTeX_Size1-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size1-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size1-Regular.ttf"},{"basename":"KaTeX_Size1-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size1-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size1-Regular.woff"},{"basename":"KaTeX_Size1-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size1-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size1-Regular.woff2"},{"basename":"KaTeX_Size2-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size2-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size2-Regular.ttf"},{"basename":"KaTeX_Size2-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size2-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size2-Regular.woff"},{"basename":"KaTeX_Size2-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size2-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size2-Regular.woff2"},{"basename":"KaTeX_Size3-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size3-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size3-Regular.ttf"},{"basename":"KaTeX_Size3-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size3-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size3-Regular.woff"},{"basename":"KaTeX_Size3-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size3-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size3-Regular.woff2"},{"basename":"KaTeX_Size4-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size4-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size4-Regular.ttf"},{"basename":"KaTeX_Size4-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size4-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size4-Regular.woff"},{"basename":"KaTeX_Size4-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Size4-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size4-Regular.woff2"},{"basename":"KaTeX_Typewriter-Regular","extname":".ttf","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Typewriter-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Typewriter-Regular.ttf"},{"basename":"KaTeX_Typewriter-Regular","extname":".woff","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Typewriter-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Typewriter-Regular.woff"},{"basename":"KaTeX_Typewriter-Regular","extname":".woff2","path":"/assets/plugins/katex.0.11.1/fonts/KaTeX_Typewriter-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Typewriter-Regular.woff2"},{"basename":"katex.min","extname":".css","path":"/assets/plugins/katex.0.11.1/katex.min.css","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"katex.min.css"},{"basename":"syntax","extname":".css","path":"/css/syntax.css","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"syntax.css"},{"basename":"KaTeX_AMS-Regular","extname":".ttf","path":"/fonts/KaTeX_AMS-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_AMS-Regular.ttf"},{"basename":"KaTeX_AMS-Regular","extname":".woff","path":"/fonts/KaTeX_AMS-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_AMS-Regular.woff"},{"basename":"KaTeX_AMS-Regular","extname":".woff2","path":"/fonts/KaTeX_AMS-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_AMS-Regular.woff2"},{"basename":"KaTeX_Caligraphic-Bold","extname":".ttf","path":"/fonts/KaTeX_Caligraphic-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Bold.ttf"},{"basename":"KaTeX_Caligraphic-Bold","extname":".woff","path":"/fonts/KaTeX_Caligraphic-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Bold.woff"},{"basename":"KaTeX_Caligraphic-Bold","extname":".woff2","path":"/fonts/KaTeX_Caligraphic-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Bold.woff2"},{"basename":"KaTeX_Caligraphic-Regular","extname":".ttf","path":"/fonts/KaTeX_Caligraphic-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Regular.ttf"},{"basename":"KaTeX_Caligraphic-Regular","extname":".woff","path":"/fonts/KaTeX_Caligraphic-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Regular.woff"},{"basename":"KaTeX_Caligraphic-Regular","extname":".woff2","path":"/fonts/KaTeX_Caligraphic-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Caligraphic-Regular.woff2"},{"basename":"KaTeX_Fraktur-Bold","extname":".ttf","path":"/fonts/KaTeX_Fraktur-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Bold.ttf"},{"basename":"KaTeX_Fraktur-Bold","extname":".woff","path":"/fonts/KaTeX_Fraktur-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Bold.woff"},{"basename":"KaTeX_Fraktur-Bold","extname":".woff2","path":"/fonts/KaTeX_Fraktur-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Bold.woff2"},{"basename":"KaTeX_Fraktur-Regular","extname":".ttf","path":"/fonts/KaTeX_Fraktur-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Regular.ttf"},{"basename":"KaTeX_Fraktur-Regular","extname":".woff","path":"/fonts/KaTeX_Fraktur-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Regular.woff"},{"basename":"KaTeX_Fraktur-Regular","extname":".woff2","path":"/fonts/KaTeX_Fraktur-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Fraktur-Regular.woff2"},{"basename":"KaTeX_Main-Bold","extname":".ttf","path":"/fonts/KaTeX_Main-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Bold.ttf"},{"basename":"KaTeX_Main-Bold","extname":".woff","path":"/fonts/KaTeX_Main-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Bold.woff"},{"basename":"KaTeX_Main-Bold","extname":".woff2","path":"/fonts/KaTeX_Main-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Bold.woff2"},{"basename":"KaTeX_Main-BoldItalic","extname":".ttf","path":"/fonts/KaTeX_Main-BoldItalic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-BoldItalic.ttf"},{"basename":"KaTeX_Main-BoldItalic","extname":".woff","path":"/fonts/KaTeX_Main-BoldItalic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-BoldItalic.woff"},{"basename":"KaTeX_Main-BoldItalic","extname":".woff2","path":"/fonts/KaTeX_Main-BoldItalic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-BoldItalic.woff2"},{"basename":"KaTeX_Main-Italic","extname":".ttf","path":"/fonts/KaTeX_Main-Italic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Italic.ttf"},{"basename":"KaTeX_Main-Italic","extname":".woff","path":"/fonts/KaTeX_Main-Italic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Italic.woff"},{"basename":"KaTeX_Main-Italic","extname":".woff2","path":"/fonts/KaTeX_Main-Italic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Italic.woff2"},{"basename":"KaTeX_Main-Regular","extname":".ttf","path":"/fonts/KaTeX_Main-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Regular.ttf"},{"basename":"KaTeX_Main-Regular","extname":".woff","path":"/fonts/KaTeX_Main-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Regular.woff"},{"basename":"KaTeX_Main-Regular","extname":".woff2","path":"/fonts/KaTeX_Main-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Main-Regular.woff2"},{"basename":"KaTeX_Math-BoldItalic","extname":".ttf","path":"/fonts/KaTeX_Math-BoldItalic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-BoldItalic.ttf"},{"basename":"KaTeX_Math-BoldItalic","extname":".woff","path":"/fonts/KaTeX_Math-BoldItalic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-BoldItalic.woff"},{"basename":"KaTeX_Math-BoldItalic","extname":".woff2","path":"/fonts/KaTeX_Math-BoldItalic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-BoldItalic.woff2"},{"basename":"KaTeX_Math-Italic","extname":".ttf","path":"/fonts/KaTeX_Math-Italic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-Italic.ttf"},{"basename":"KaTeX_Math-Italic","extname":".woff","path":"/fonts/KaTeX_Math-Italic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-Italic.woff"},{"basename":"KaTeX_Math-Italic","extname":".woff2","path":"/fonts/KaTeX_Math-Italic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Math-Italic.woff2"},{"basename":"KaTeX_SansSerif-Bold","extname":".ttf","path":"/fonts/KaTeX_SansSerif-Bold.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Bold.ttf"},{"basename":"KaTeX_SansSerif-Bold","extname":".woff","path":"/fonts/KaTeX_SansSerif-Bold.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Bold.woff"},{"basename":"KaTeX_SansSerif-Bold","extname":".woff2","path":"/fonts/KaTeX_SansSerif-Bold.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Bold.woff2"},{"basename":"KaTeX_SansSerif-Italic","extname":".ttf","path":"/fonts/KaTeX_SansSerif-Italic.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Italic.ttf"},{"basename":"KaTeX_SansSerif-Italic","extname":".woff","path":"/fonts/KaTeX_SansSerif-Italic.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Italic.woff"},{"basename":"KaTeX_SansSerif-Italic","extname":".woff2","path":"/fonts/KaTeX_SansSerif-Italic.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Italic.woff2"},{"basename":"KaTeX_SansSerif-Regular","extname":".ttf","path":"/fonts/KaTeX_SansSerif-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Regular.ttf"},{"basename":"KaTeX_SansSerif-Regular","extname":".woff","path":"/fonts/KaTeX_SansSerif-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Regular.woff"},{"basename":"KaTeX_SansSerif-Regular","extname":".woff2","path":"/fonts/KaTeX_SansSerif-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_SansSerif-Regular.woff2"},{"basename":"KaTeX_Script-Regular","extname":".ttf","path":"/fonts/KaTeX_Script-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Script-Regular.ttf"},{"basename":"KaTeX_Script-Regular","extname":".woff","path":"/fonts/KaTeX_Script-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Script-Regular.woff"},{"basename":"KaTeX_Script-Regular","extname":".woff2","path":"/fonts/KaTeX_Script-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Script-Regular.woff2"},{"basename":"KaTeX_Size1-Regular","extname":".ttf","path":"/fonts/KaTeX_Size1-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size1-Regular.ttf"},{"basename":"KaTeX_Size1-Regular","extname":".woff","path":"/fonts/KaTeX_Size1-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size1-Regular.woff"},{"basename":"KaTeX_Size1-Regular","extname":".woff2","path":"/fonts/KaTeX_Size1-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size1-Regular.woff2"},{"basename":"KaTeX_Size2-Regular","extname":".ttf","path":"/fonts/KaTeX_Size2-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size2-Regular.ttf"},{"basename":"KaTeX_Size2-Regular","extname":".woff","path":"/fonts/KaTeX_Size2-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size2-Regular.woff"},{"basename":"KaTeX_Size2-Regular","extname":".woff2","path":"/fonts/KaTeX_Size2-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size2-Regular.woff2"},{"basename":"KaTeX_Size3-Regular","extname":".ttf","path":"/fonts/KaTeX_Size3-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size3-Regular.ttf"},{"basename":"KaTeX_Size3-Regular","extname":".woff","path":"/fonts/KaTeX_Size3-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size3-Regular.woff"},{"basename":"KaTeX_Size3-Regular","extname":".woff2","path":"/fonts/KaTeX_Size3-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size3-Regular.woff2"},{"basename":"KaTeX_Size4-Regular","extname":".ttf","path":"/fonts/KaTeX_Size4-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size4-Regular.ttf"},{"basename":"KaTeX_Size4-Regular","extname":".woff","path":"/fonts/KaTeX_Size4-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size4-Regular.woff"},{"basename":"KaTeX_Size4-Regular","extname":".woff2","path":"/fonts/KaTeX_Size4-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Size4-Regular.woff2"},{"basename":"KaTeX_Typewriter-Regular","extname":".ttf","path":"/fonts/KaTeX_Typewriter-Regular.ttf","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Typewriter-Regular.ttf"},{"basename":"KaTeX_Typewriter-Regular","extname":".woff","path":"/fonts/KaTeX_Typewriter-Regular.woff","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Typewriter-Regular.woff"},{"basename":"KaTeX_Typewriter-Regular","extname":".woff2","path":"/fonts/KaTeX_Typewriter-Regular.woff2","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"KaTeX_Typewriter-Regular.woff2"},{"basename":"avatar","extname":".jpg","path":"/img/about/avatar.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"avatar.jpg"},{"basename":"photo_1","extname":".jpg","path":"/img/about/photo_1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_1.jpg"},{"basename":"photo_10","extname":".jpg","path":"/img/about/photo_10.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_10.jpg"},{"basename":"photo_2","extname":".jpg","path":"/img/about/photo_2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_2.jpg"},{"basename":"photo_3","extname":".jpg","path":"/img/about/photo_3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_3.jpg"},{"basename":"photo_4","extname":".jpg","path":"/img/about/photo_4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_4.jpg"},{"basename":"photo_5","extname":".jpg","path":"/img/about/photo_5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_5.jpg"},{"basename":"photo_6","extname":".jpg","path":"/img/about/photo_6.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_6.jpg"},{"basename":"photo_7","extname":".JPG","path":"/img/about/photo_7.JPG","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_7.JPG"},{"basename":"photo_8","extname":".JPG","path":"/img/about/photo_8.JPG","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_8.JPG"},{"basename":"photo_9","extname":".jpg","path":"/img/about/photo_9.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"photo_9.jpg"},{"basename":"2023-01-09-agi-llm-tech-1","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-1.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-1.jpeg"},{"basename":"2023-01-09-agi-llm-tech-10","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-10.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-10.jpeg"},{"basename":"2023-01-09-agi-llm-tech-11","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-11.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-11.jpeg"},{"basename":"2023-01-09-agi-llm-tech-12","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-12.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-12.jpeg"},{"basename":"2023-01-09-agi-llm-tech-13","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-13.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-13.jpeg"},{"basename":"2023-01-09-agi-llm-tech-14","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-14.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-14.jpeg"},{"basename":"2023-01-09-agi-llm-tech-15","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-15.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-15.jpeg"},{"basename":"2023-01-09-agi-llm-tech-16","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-16.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-16.jpeg"},{"basename":"2023-01-09-agi-llm-tech-17","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-17.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-17.jpeg"},{"basename":"2023-01-09-agi-llm-tech-18","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-18.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-18.jpeg"},{"basename":"2023-01-09-agi-llm-tech-2","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-2.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-2.jpeg"},{"basename":"2023-01-09-agi-llm-tech-3","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-3.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-3.jpeg"},{"basename":"2023-01-09-agi-llm-tech-4","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-4.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-4.jpeg"},{"basename":"2023-01-09-agi-llm-tech-5","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-5.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-5.jpeg"},{"basename":"2023-01-09-agi-llm-tech-6","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-6.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-6.jpeg"},{"basename":"2023-01-09-agi-llm-tech-7","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-7.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-7.jpeg"},{"basename":"2023-01-09-agi-llm-tech-8","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-8.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-8.jpeg"},{"basename":"2023-01-09-agi-llm-tech-9","extname":".jpeg","path":"/img/backup/2023-01-09-agi-llm-tech-9.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-09-agi-llm-tech-9.jpeg"},{"basename":"02-璀璨-03","extname":".jpg","path":"/img/design/02-璀璨-03.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"02-璀璨-03.jpg"},{"basename":"04-LISMIS内卡片V5-正面","extname":".jpg","path":"/img/design/04-LISMIS内卡片V5-正面.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"04-LISMIS内卡片V5-正面.jpg"},{"basename":"09_2019款_1_3","extname":".jpg","path":"/img/design/09_2019款_1_3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"09_2019款_1_3.jpg"},{"basename":"09_2019款_1_4","extname":".jpg","path":"/img/design/09_2019款_1_4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"09_2019款_1_4.jpg"},{"basename":"09_2019款_1_5","extname":".jpg","path":"/img/design/09_2019款_1_5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"09_2019款_1_5.jpg"},{"basename":"WB_软心柏林_1","extname":".jpg","path":"/img/design/WB_软心柏林_1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"WB_软心柏林_1.jpg"},{"basename":"club-app-smartisan","extname":".jpg","path":"/img/design/club-app-smartisan.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"club-app-smartisan.jpg"},{"basename":"ica-001","extname":".png","path":"/img/design/ica-001.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"ica-001.png"},{"basename":"ica-002","extname":".png","path":"/img/design/ica-002.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"ica-002.png"},{"basename":"ica-003","extname":".png","path":"/img/design/ica-003.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"ica-003.png"},{"basename":"ica-004","extname":".png","path":"/img/design/ica-004.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"ica-004.png"},{"basename":"lismis-chocobble-001","extname":".jpg","path":"/img/design/lismis-chocobble-001.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-001.jpg"},{"basename":"lismis-chocobble-002","extname":".jpg","path":"/img/design/lismis-chocobble-002.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-002.jpg"},{"basename":"lismis-chocobble-003","extname":".jpg","path":"/img/design/lismis-chocobble-003.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-003.jpg"},{"basename":"lismis-chocobble-15jar-1","extname":".jpg","path":"/img/design/lismis-chocobble-15jar-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-15jar-1.jpg"},{"basename":"lismis-chocobble-15jar-2","extname":".jpg","path":"/img/design/lismis-chocobble-15jar-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-15jar-2.jpg"},{"basename":"lismis-chocobble-15jar-3","extname":".jpg","path":"/img/design/lismis-chocobble-15jar-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-15jar-3.jpg"},{"basename":"lismis-chocobble-4jar-1","extname":".jpg","path":"/img/design/lismis-chocobble-4jar-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-4jar-1.jpg"},{"basename":"lismis-chocobble-4jar-2","extname":".jpg","path":"/img/design/lismis-chocobble-4jar-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-4jar-2.jpg"},{"basename":"lismis-chocobble-4jar-3","extname":".jpg","path":"/img/design/lismis-chocobble-4jar-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-4jar-3.jpg"},{"basename":"lismis-chocobble-8jar-1","extname":".jpg","path":"/img/design/lismis-chocobble-8jar-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-8jar-1.jpg"},{"basename":"lismis-chocobble-8jar-2","extname":".jpg","path":"/img/design/lismis-chocobble-8jar-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-8jar-2.jpg"},{"basename":"lismis-chocobble-8jar-3","extname":".jpg","path":"/img/design/lismis-chocobble-8jar-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-chocobble-8jar-3.jpg"},{"basename":"lismis-cube03-1","extname":".jpg","path":"/img/design/lismis-cube03-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-cube03-1.jpg"},{"basename":"lismis-cube03-2","extname":".jpg","path":"/img/design/lismis-cube03-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-cube03-2.jpg"},{"basename":"lismis-cube03-3","extname":".jpg","path":"/img/design/lismis-cube03-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-cube03-3.jpg"},{"basename":"lismis-vi-001","extname":".jpg","path":"/img/design/lismis-vi-001.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-001.jpg"},{"basename":"lismis-vi-002","extname":".jpg","path":"/img/design/lismis-vi-002.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-002.jpg"},{"basename":"lismis-vi-003","extname":".jpg","path":"/img/design/lismis-vi-003.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-003.jpg"},{"basename":"lismis-vi-004","extname":".jpg","path":"/img/design/lismis-vi-004.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-004.jpg"},{"basename":"lismis-vi-005","extname":".jpg","path":"/img/design/lismis-vi-005.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-005.jpg"},{"basename":"lismis-vi-006","extname":".jpg","path":"/img/design/lismis-vi-006.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-006.jpg"},{"basename":"lismis-vi-007","extname":".jpg","path":"/img/design/lismis-vi-007.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-007.jpg"},{"basename":"lismis-vi-008","extname":".jpg","path":"/img/design/lismis-vi-008.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-008.jpg"},{"basename":"lismis-vi-009","extname":".jpg","path":"/img/design/lismis-vi-009.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"lismis-vi-009.jpg"},{"basename":"favicon","extname":".png","path":"/img/favicon.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"favicon.png"},{"basename":"img-test","extname":".png","path":"/img/img-test.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"img-test.png"},{"basename":"monochrome-mobile","extname":".png","path":"/img/monochrome-mobile.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"monochrome-mobile.png"},{"basename":"monochrome","extname":".svg","path":"/img/monochrome.svg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"monochrome.svg"},{"basename":"monochrome01","extname":".png","path":"/img/monochrome01.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"monochrome01.png"},{"basename":"2012-04-24-openrtmfp-cumulus-4-1","extname":".png","path":"/img/src/2012-04-24-openrtmfp-cumulus-4-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-04-24-openrtmfp-cumulus-4-1.png"},{"basename":"2012-04-24-openrtmfp-cumulus-4-2","extname":".png","path":"/img/src/2012-04-24-openrtmfp-cumulus-4-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-04-24-openrtmfp-cumulus-4-2.png"},{"basename":"2012-06-07-openrtmfp-cumulus-6-1","extname":".png","path":"/img/src/2012-06-07-openrtmfp-cumulus-6-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-06-07-openrtmfp-cumulus-6-1.png"},{"basename":"2012-06-07-openrtmfp-cumulus-6-2","extname":".png","path":"/img/src/2012-06-07-openrtmfp-cumulus-6-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-06-07-openrtmfp-cumulus-6-2.png"},{"basename":"2012-06-07-openrtmfp-cumulus-6-3","extname":".png","path":"/img/src/2012-06-07-openrtmfp-cumulus-6-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-06-07-openrtmfp-cumulus-6-3.png"},{"basename":"2012-06-25-openrtmfp-cumulus-7-1","extname":".png","path":"/img/src/2012-06-25-openrtmfp-cumulus-7-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-06-25-openrtmfp-cumulus-7-1.png"},{"basename":"2012-06-25-openrtmfp-cumulus-7-2","extname":".png","path":"/img/src/2012-06-25-openrtmfp-cumulus-7-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-06-25-openrtmfp-cumulus-7-2.png"},{"basename":"2012-06-25-openrtmfp-cumulus-7-3","extname":".png","path":"/img/src/2012-06-25-openrtmfp-cumulus-7-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2012-06-25-openrtmfp-cumulus-7-3.png"},{"basename":"2020-04-15-covid2019-catering-business-mode-1","extname":".jpg","path":"/img/src/2020-04-15-covid2019-catering-business-mode-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-04-15-covid2019-catering-business-mode-1.jpg"},{"basename":"2020-06-11-captain-alibaba-1","extname":".png","path":"/img/src/2020-06-11-captain-alibaba-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-06-11-captain-alibaba-1.png"},{"basename":"2020-06-11-captain-alibaba-2","extname":".png","path":"/img/src/2020-06-11-captain-alibaba-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-06-11-captain-alibaba-2.png"},{"basename":"2020-06-11-captain-alibaba-3","extname":".png","path":"/img/src/2020-06-11-captain-alibaba-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-06-11-captain-alibaba-3.png"},{"basename":"2020-06-11-captain-alibaba-4","extname":".png","path":"/img/src/2020-06-11-captain-alibaba-4.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-06-11-captain-alibaba-4.png"},{"basename":"2020-06-11-captain-alibaba-5","extname":".png","path":"/img/src/2020-06-11-captain-alibaba-5.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-06-11-captain-alibaba-5.png"},{"basename":"2020-06-11-captain-alibaba-6","extname":".png","path":"/img/src/2020-06-11-captain-alibaba-6.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-06-11-captain-alibaba-6.png"},{"basename":"2020-11-11-captain-double-eleven-1","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-1.jpg"},{"basename":"2020-11-11-captain-double-eleven-10","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-10.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-10.jpg"},{"basename":"2020-11-11-captain-double-eleven-11","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-11.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-11.jpg"},{"basename":"2020-11-11-captain-double-eleven-2","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-2.jpg"},{"basename":"2020-11-11-captain-double-eleven-3","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-3.jpg"},{"basename":"2020-11-11-captain-double-eleven-4","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-4.jpg"},{"basename":"2020-11-11-captain-double-eleven-5","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-5.jpg"},{"basename":"2020-11-11-captain-double-eleven-6","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-6.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-6.jpg"},{"basename":"2020-11-11-captain-double-eleven-7","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-7.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-7.jpg"},{"basename":"2020-11-11-captain-double-eleven-8","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-8.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-8.jpg"},{"basename":"2020-11-11-captain-double-eleven-9","extname":".jpg","path":"/img/src/2020-11-11-captain-double-eleven-9.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2020-11-11-captain-double-eleven-9.jpg"},{"basename":"2021-11-11-captain-tttm-1","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-1.jpg"},{"basename":"2021-11-11-captain-tttm-10","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-10.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-10.jpg"},{"basename":"2021-11-11-captain-tttm-11","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-11.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-11.jpg"},{"basename":"2021-11-11-captain-tttm-2","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-2.jpg"},{"basename":"2021-11-11-captain-tttm-3","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-3.jpg"},{"basename":"2021-11-11-captain-tttm-4","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-4.jpg"},{"basename":"2021-11-11-captain-tttm-5","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-5.jpg"},{"basename":"2021-11-11-captain-tttm-6","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-6.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-6.jpg"},{"basename":"2021-11-11-captain-tttm-7","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-7.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-7.jpg"},{"basename":"2021-11-11-captain-tttm-8","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-8.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-8.jpg"},{"basename":"2021-11-11-captain-tttm-9","extname":".jpg","path":"/img/src/2021-11-11-captain-tttm-9.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2021-11-11-captain-tttm-9.jpg"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-1","extname":".gif","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-1.gif"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-2","extname":".png","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-2.png"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-3","extname":".png","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-3.png"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-4","extname":".gif","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-4.gif"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-5","extname":".png","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-5.png"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-6","extname":".png","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-6.png"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-7","extname":".gif","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-7.gif"},{"basename":"2022-04-05-pathways-language-model-palm-scaling-to-8","extname":".png","path":"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-04-05-pathways-language-model-palm-scaling-to-8.png"},{"basename":"2022-07-27-captain-birthday-1","extname":".png","path":"/img/src/2022-07-27-captain-birthday-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-07-27-captain-birthday-1.png"},{"basename":"2022-12-11-wechat-chatgpt-1","extname":".png","path":"/img/src/2022-12-11-wechat-chatgpt-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-11-wechat-chatgpt-1.png"},{"basename":"2022-12-11-wechat-chatgpt-2","extname":".png","path":"/img/src/2022-12-11-wechat-chatgpt-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-11-wechat-chatgpt-2.png"},{"basename":"2022-12-11-wechat-chatgpt-3","extname":".png","path":"/img/src/2022-12-11-wechat-chatgpt-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-11-wechat-chatgpt-3.png"},{"basename":"2022-12-16-midjourney-first-test-1","extname":".png","path":"/img/src/2022-12-16-midjourney-first-test-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-16-midjourney-first-test-1.png"},{"basename":"2022-12-16-midjourney-first-test-2","extname":".png","path":"/img/src/2022-12-16-midjourney-first-test-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-16-midjourney-first-test-2.png"},{"basename":"2022-12-16-midjourney-first-test-3","extname":".png","path":"/img/src/2022-12-16-midjourney-first-test-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-16-midjourney-first-test-3.png"},{"basename":"2022-12-17-ai-bert-1-1","extname":".jpg","path":"/img/src/2022-12-17-ai-bert-1-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-17-ai-bert-1-1.jpg"},{"basename":"2022-12-19-language-model-1","extname":".png","path":"/img/src/2022-12-19-language-model-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-19-language-model-1.png"},{"basename":"2022-12-19-language-model-2","extname":".png","path":"/img/src/2022-12-19-language-model-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-19-language-model-2.png"},{"basename":"2022-12-21-build-github-pages-with-jekyll-1","extname":".png","path":"/img/src/2022-12-21-build-github-pages-with-jekyll-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-21-build-github-pages-with-jekyll-1.png"},{"basename":"2022-12-21-build-github-pages-with-jekyll-2","extname":".png","path":"/img/src/2022-12-21-build-github-pages-with-jekyll-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-21-build-github-pages-with-jekyll-2.png"},{"basename":"2022-12-24-captain-nlp-1","extname":".png","path":"/img/src/2022-12-24-captain-nlp-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-1.png"},{"basename":"2022-12-24-captain-nlp-2","extname":".jpg","path":"/img/src/2022-12-24-captain-nlp-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-2.jpg"},{"basename":"2022-12-24-captain-nlp-3","extname":".jpg","path":"/img/src/2022-12-24-captain-nlp-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-3.jpg"},{"basename":"2022-12-24-captain-nlp-4","extname":".png","path":"/img/src/2022-12-24-captain-nlp-4.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-4.png"},{"basename":"2022-12-24-captain-nlp-5","extname":".png","path":"/img/src/2022-12-24-captain-nlp-5.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-5.png"},{"basename":"2022-12-24-captain-nlp-6","extname":".png","path":"/img/src/2022-12-24-captain-nlp-6.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-6.png"},{"basename":"2022-12-24-captain-nlp-7","extname":".png","path":"/img/src/2022-12-24-captain-nlp-7.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-7.png"},{"basename":"2022-12-24-captain-nlp-8","extname":".png","path":"/img/src/2022-12-24-captain-nlp-8.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-8.png"},{"basename":"2022-12-24-captain-nlp-9","extname":".png","path":"/img/src/2022-12-24-captain-nlp-9.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-24-captain-nlp-9.png"},{"basename":"2022-12-25-captain-cv-1","extname":".jpeg","path":"/img/src/2022-12-25-captain-cv-1.jpeg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2022-12-25-captain-cv-1.jpeg"},{"basename":"2023-01-04-captain-nlp-5","extname":".png","path":"/img/src/2023-01-04-captain-nlp-5.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-captain-nlp-5.png"},{"basename":"2023-01-04-language-model-5-1","extname":".png","path":"/img/src/2023-01-04-language-model-5-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-1.png"},{"basename":"2023-01-04-language-model-5-10","extname":".png","path":"/img/src/2023-01-04-language-model-5-10.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-10.png"},{"basename":"2023-01-04-language-model-5-11","extname":".gif","path":"/img/src/2023-01-04-language-model-5-11.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-11.gif"},{"basename":"2023-01-04-language-model-5-12","extname":".png","path":"/img/src/2023-01-04-language-model-5-12.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-12.png"},{"basename":"2023-01-04-language-model-5-13","extname":".png","path":"/img/src/2023-01-04-language-model-5-13.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-13.png"},{"basename":"2023-01-04-language-model-5-14","extname":".png","path":"/img/src/2023-01-04-language-model-5-14.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-14.png"},{"basename":"2023-01-04-language-model-5-15","extname":".png","path":"/img/src/2023-01-04-language-model-5-15.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-15.png"},{"basename":"2023-01-04-language-model-5-16","extname":".png","path":"/img/src/2023-01-04-language-model-5-16.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-16.png"},{"basename":"2023-01-04-language-model-5-17","extname":".gif","path":"/img/src/2023-01-04-language-model-5-17.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-17.gif"},{"basename":"2023-01-04-language-model-5-2","extname":".png","path":"/img/src/2023-01-04-language-model-5-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-2.png"},{"basename":"2023-01-04-language-model-5-3","extname":".png","path":"/img/src/2023-01-04-language-model-5-3.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-3.png"},{"basename":"2023-01-04-language-model-5-4","extname":".png","path":"/img/src/2023-01-04-language-model-5-4.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-4.png"},{"basename":"2023-01-04-language-model-5-5","extname":".png","path":"/img/src/2023-01-04-language-model-5-5.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-5.png"},{"basename":"2023-01-04-language-model-5-6","extname":".png","path":"/img/src/2023-01-04-language-model-5-6.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-6.png"},{"basename":"2023-01-04-language-model-5-7","extname":".png","path":"/img/src/2023-01-04-language-model-5-7.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-7.png"},{"basename":"2023-01-04-language-model-5-8","extname":".png","path":"/img/src/2023-01-04-language-model-5-8.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-8.png"},{"basename":"2023-01-04-language-model-5-9","extname":".png","path":"/img/src/2023-01-04-language-model-5-9.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-04-language-model-5-9.png"},{"basename":"2023-01-12-generative-ai-revolution-in-games-0","extname":".png","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-0.png"},{"basename":"2023-01-12-generative-ai-revolution-in-games-1","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-1.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-2","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-2.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-3","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-3.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-4","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-4.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-5","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-5.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-6","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-6.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-7","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-7.jpg"},{"basename":"2023-01-12-generative-ai-revolution-in-games-8","extname":".jpg","path":"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-12-generative-ai-revolution-in-games-8.jpg"},{"basename":"2023-01-15-antler-generative-ai-1","extname":".jpg","path":"/img/src/2023-01-15-antler-generative-ai-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-15-antler-generative-ai-1.jpg"},{"basename":"2023-01-15-antler-generative-ai-2","extname":".jpg","path":"/img/src/2023-01-15-antler-generative-ai-2.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-15-antler-generative-ai-2.jpg"},{"basename":"2023-01-15-antler-generative-ai-3","extname":".jpg","path":"/img/src/2023-01-15-antler-generative-ai-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-15-antler-generative-ai-3.jpg"},{"basename":"2023-01-15-antler-generative-ai-4","extname":".jpg","path":"/img/src/2023-01-15-antler-generative-ai-4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-15-antler-generative-ai-4.jpg"},{"basename":"2023-01-15-antler-generative-ai-5","extname":".jpg","path":"/img/src/2023-01-15-antler-generative-ai-5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-15-antler-generative-ai-5.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-1","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-1.png"},{"basename":"2023-01-17-juergen-deep-learning-history-10","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-10.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-10.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-11","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-11.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-11.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-12","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-12.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-12.png"},{"basename":"2023-01-17-juergen-deep-learning-history-13","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-13.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-13.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-14","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-14.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-14.png"},{"basename":"2023-01-17-juergen-deep-learning-history-15","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-15.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-15.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-16","extname":".gif","path":"/img/src/2023-01-17-juergen-deep-learning-history-16.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-16.gif"},{"basename":"2023-01-17-juergen-deep-learning-history-17","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-17.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-17.png"},{"basename":"2023-01-17-juergen-deep-learning-history-18","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-18.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-19","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-19.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-19.png"},{"basename":"2023-01-17-juergen-deep-learning-history-2","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-2.png"},{"basename":"2023-01-17-juergen-deep-learning-history-20","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-20.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-20.png"},{"basename":"2023-01-17-juergen-deep-learning-history-21","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-21.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-22","extname":".gif","path":"/img/src/2023-01-17-juergen-deep-learning-history-22.gif","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-22.gif"},{"basename":"2023-01-17-juergen-deep-learning-history-23","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-23.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-23.png"},{"basename":"2023-01-17-juergen-deep-learning-history-24","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-24.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-24.png"},{"basename":"2023-01-17-juergen-deep-learning-history-25","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-25.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-25.png"},{"basename":"2023-01-17-juergen-deep-learning-history-26","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-26.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-26.png"},{"basename":"2023-01-17-juergen-deep-learning-history-27","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-27.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-27.png"},{"basename":"2023-01-17-juergen-deep-learning-history-28","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-28.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-28.png"},{"basename":"2023-01-17-juergen-deep-learning-history-29","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-29.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-29.png"},{"basename":"2023-01-17-juergen-deep-learning-history-3","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-3.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-30","extname":".png","path":"/img/src/2023-01-17-juergen-deep-learning-history-30.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-30.png"},{"basename":"2023-01-17-juergen-deep-learning-history-31","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-31.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-4","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-4.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-4.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-5","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-5.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-5.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-6","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-6.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-6.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-7","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-7.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-7.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-8","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-8.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-8.jpg"},{"basename":"2023-01-17-juergen-deep-learning-history-9","extname":".jpg","path":"/img/src/2023-01-17-juergen-deep-learning-history-9.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-17-juergen-deep-learning-history-9.jpg"},{"basename":"2023-01-24-openai-api","extname":".png","path":"/img/src/2023/2023-01-24-openai-api.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-24-openai-api.png"},{"basename":"2023-01-29-ata-headline-top-1","extname":".jpg","path":"/img/src/2023/2023-01-29-ata-headline-top-1.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-01-29-ata-headline-top-1.jpg"},{"basename":"2023-02-03-damus-850x478","extname":".jpg","path":"/img/src/2023/2023-02-03-damus-850x478.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-02-03-damus-850x478.jpg"},{"basename":"2023-02-03-damus-bic_jackdorsey_neutral","extname":".jpg","path":"/img/src/2023/2023-02-03-damus-bic_jackdorsey_neutral.jpg","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-02-03-damus-bic_jackdorsey_neutral.jpg"},{"basename":"2023-02-03-damus-tw-1","extname":".png","path":"/img/src/2023/2023-02-03-damus-tw-1.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-02-03-damus-tw-1.png"},{"basename":"2023-02-03-damus-tw-2","extname":".png","path":"/img/src/2023/2023-02-03-damus-tw-2.png","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"2023-02-03-damus-tw-2.png"},{"basename":"main","extname":".js","path":"/js/main.js","collection":null,"modified_time":"2023-02-08 17:28:06 +0000","name":"main.js"}],"categories":{"rt_tech":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 9:关键线程逻辑分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本文对 RTMFPServer 线程、RTMFPManager 对 RTMFPServer 的影响进行源码解读。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 9:关键线程逻辑分析</h2>\t\t\n\t<time datetime=\"2012-08-04T17:58:17+00:00\" class=\"by-line\">04 Aug 2012, 广州 | 麦克船长 | 总计 5236 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一rtmfpserver-线程的启动和等待\" id=\"markdown-toc-一rtmfpserver-线程的启动和等待\">一、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程的启动和等待</a> <ul>\n <li><a href=\"#1pocothread\" id=\"markdown-toc-1pocothread\">1、<code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code></a></li>\n <li><a href=\"#2封装一个可运行线程的类\" id=\"markdown-toc-2封装一个可运行线程的类\">2、封装一个可运行线程的类</a></li>\n <li><a href=\"#3启动-rtmfpserver-线程\" id=\"markdown-toc-3启动-rtmfpserver-线程\">3、启动 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程</a></li>\n <li><a href=\"#4rtmfpserver-线程等待\" id=\"markdown-toc-4rtmfpserver-线程等待\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程等待</a></li>\n </ul>\n </li>\n <li><a href=\"#二rtmfpmanager-对-rtmfpserver-的影响\" id=\"markdown-toc-二rtmfpmanager-对-rtmfpserver-的影响\">二、<code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 对 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的影响</a></li>\n</ul>\n\n<h3 id=\"一rtmfpserver-线程的启动和等待\">一、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程的启动和等待</h3>\n\n<h4 id=\"1pocothread\">1、<code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code></h4>\n\n<p>Cumulus 大量使用了 <code class=\"language-plaintext highlighter-rouge\">Poco</code> 的线程库。一个简单的 Poco 线程的使用实例如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">PoechantRunnable</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Runnable</span> <span class=\"p\">{</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">void</span> <span class=\"n\">run</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"c1\">// your codes</span>\n <span class=\"p\">}</span>\n<span class=\"p\">};</span>\n \n<span class=\"kt\">int</span> <span class=\"nf\">main</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">PoechantRunnable</span> <span class=\"n\">runnable</span><span class=\"p\">;</span> <span class=\"c1\">// Image that it's a gift</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Thread</span> <span class=\"kr\">thread</span><span class=\"p\">;</span> <span class=\"c1\">// And… thread is just like your girl</span>\n <span class=\"kr\">thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">runnable</span><span class=\"p\">);</span> <span class=\"c1\">// Okay, give your sweet babe the gift :)</span>\n <span class=\"kr\">thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2封装一个可运行线程的类\">2、封装一个可运行线程的类</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中实现了一个 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 类,该类继承了 <code class=\"language-plaintext highlighter-rouge\">Runnable</code>,就是上面那个 <code class=\"language-plaintext highlighter-rouge\">gift</code> 喽。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">StartableProcess</span> <span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Runnable</span><span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">StartableProcess</span><span class=\"p\">(</span><span class=\"n\">Startable</span><span class=\"o\">&</span> <span class=\"n\">startable</span><span class=\"p\">);</span>\n<span class=\"nl\">private:</span>\n <span class=\"kt\">void</span> <span class=\"n\">run</span><span class=\"p\">();</span>\n <span class=\"n\">Startable</span><span class=\"o\">&</span> <span class=\"n\">_startable</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>可以看到其中有 <code class=\"language-plaintext highlighter-rouge\">Startable& _startable</code> 引用成员,它并没有继承 <code class=\"language-plaintext highlighter-rouge\">Runnable</code>,而是封装了 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 和 <code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Thread</span> <span class=\"kr\">_thread</span><span class=\"p\">;</span>\n<span class=\"n\">StartableProcess</span> <span class=\"n\">_process</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>这里 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 封装了一个 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 成员,与 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 是有所区别的。接下俩我们看他们是怎么用的。</p>\n\n<h4 id=\"3启动-rtmfpserver-线程\">3、启动 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程</h4>\n<p>我们可以看到在 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 类的构造函数中初始化了 <code class=\"language-plaintext highlighter-rouge\">_process</code> 成员,初始化线程成员并传入线程名,设定标志域 <code class=\"language-plaintext highlighter-rouge\">(Flag Field)_stop</code> 为 <code class=\"language-plaintext highlighter-rouge\">true</code>,因为它还没有调用启动函数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">Startable</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_name</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">),</span>\n <span class=\"kr\">_thread</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">),</span>\n <span class=\"n\">_stop</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">),</span>\n <span class=\"n\">_haveToJoin</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">_process</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>初始化 <code class=\"language-plaintext highlighter-rouge\">_process</code> 时,调用 <code class=\"language-plaintext highlighter-rouge\">StartableProcess</code> 构造函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">StartableProcess</span><span class=\"o\">::</span><span class=\"n\">StartableProcess</span><span class=\"p\">(</span><span class=\"n\">Startable</span><span class=\"o\">&</span> <span class=\"n\">startable</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_startable</span><span class=\"p\">(</span><span class=\"n\">startable</span><span class=\"p\">){</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>传入 <code class=\"language-plaintext highlighter-rouge\">_startable</code> 的引用。在 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中所有的线程的可运行类都是继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 类的,然后通过调用 <code class=\"language-plaintext highlighter-rouge\">start()</code> 来启动,启动后会响应到 <code class=\"language-plaintext highlighter-rouge\">run()</code>。下面我们以 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程为例。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 类是继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 类的:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">RTMFPServer</span>\n <span class=\"o\">:</span> <span class=\"k\">private</span> <span class=\"n\">Gateway</span><span class=\"p\">,</span>\n <span class=\"k\">protected</span> <span class=\"n\">Handler</span><span class=\"p\">,</span>\n <span class=\"k\">private</span> <span class=\"n\">Startable</span><span class=\"p\">,</span>\n <span class=\"k\">private</span> <span class=\"n\">SocketHandler</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的构造函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">RTMFPServer</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">cores</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">Startable</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer\"</span><span class=\"p\">),</span>\n <span class=\"n\">_sendingEngine</span><span class=\"p\">(</span><span class=\"n\">cores</span><span class=\"p\">),</span>\n <span class=\"n\">_receivingEngine</span><span class=\"p\">(</span><span class=\"n\">cores</span><span class=\"p\">),</span>\n <span class=\"n\">_pCirrus</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">_handshake</span><span class=\"p\">(</span><span class=\"n\">_receivingEngine</span><span class=\"p\">,</span>\n <span class=\"n\">_sendingEngine</span><span class=\"p\">,</span>\n <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">),</span>\n <span class=\"n\">_sessions</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>其中在初始化时调用了其父类的构造函数。接下来就要启动RTMFPServer线程了。</p>\n\n<table>\n <thead>\n <tr>\n <th>所在线程</th>\n <th>调用者</th>\n <th>函数</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>主线程</td>\n <td>main(…)</td>\n <td> </td>\n </tr>\n <tr>\n <td>主线程</td>\n <td>RTMFPServer对象</td>\n <td>RTMFPServer::start()</td>\n </tr>\n <tr>\n <td>主线程</td>\n <td>RTMFPServer对象</td>\n <td>Startable::start()</td>\n </tr>\n <tr>\n <td>主线程</td>\n <td>RTMFPServer从Startable继承来的Thread成员</td>\n <td>Thread::start(…)</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象从Startable继承来的StartableProcess成员</td>\n <td>StartableProcess::run()</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象</td>\n <td>RTMFPServer::prerun()</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象</td>\n <td>Startable::prerun()</td>\n </tr>\n <tr>\n <td>RTMFPServer</td>\n <td>RTMFPServer对象</td>\n <td>RTMFPServer::run()</td>\n </tr>\n </tbody>\n</table>\n\n<h4 id=\"4rtmfpserver-线程等待\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程等待</h4>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run()</code> 实现线程的持续运行,主要是依靠这两行代码:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">while</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">terminate</span><span class=\"p\">)</span>\n <span class=\"n\">handle</span><span class=\"p\">(</span><span class=\"n\">terminate</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">handle(…)</code> 函数很简单,如下只进行了 <code class=\"language-plaintext highlighter-rouge\">sleep(...)</code> 和 <code class=\"language-plaintext highlighter-rouge\">giveHandle()</code> 两个操作。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">handle</span><span class=\"p\">(</span><span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">){</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">sleep</span><span class=\"p\">()</span> <span class=\"o\">!=</span> <span class=\"n\">STOP</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">giveHandle</span><span class=\"p\">();</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span>\n <span class=\"n\">terminate</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">sleep(…)</code> 是 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 是从 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 继承而来的,声明如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">WakeUpType</span> <span class=\"nf\">sleep</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">timeout</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>定义如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">WakeUpType</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">timeout</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"n\">WakeUpType</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">WAKEUP</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">tryWait</span><span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"p\">))</span>\n <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">TIMEOUT</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">wait</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>在运行状态下,<code class=\"language-plaintext highlighter-rouge\">_stop</code> 为 <code class=\"language-plaintext highlighter-rouge\">false</code>,而默认参数 <code class=\"language-plaintext highlighter-rouge\">timeout</code> 为 0,所以会调用:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">wait</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>这个 <code class=\"language-plaintext highlighter-rouge\">_wakeUpEvent</code> 成员是一个 <code class=\"language-plaintext highlighter-rouge\">Poco::Event</code> 对象,<code class=\"language-plaintext highlighter-rouge\">Poco::Event</code> 有一个使用方式就是在调用 <code class=\"language-plaintext highlighter-rouge\">Poco::Event::wait()</code> 后,会一直等待 <code class=\"language-plaintext highlighter-rouge\">Poco::Event::set()</code> 被调用后,才会跳出 <code class=\"language-plaintext highlighter-rouge\">wait</code> 的状态。在 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中 <code class=\"language-plaintext highlighter-rouge\">set</code> 的动作是由:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">RTMFPServer::requestHandle()</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">PoolThread::push(Poco::AutoPtr<RunnableType>& pRunnable)</code></li>\n</ul>\n\n<p>执行的。</p>\n\n<h3 id=\"二rtmfpmanager-对-rtmfpserver-的影响\">二、<code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 对 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的影响</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 与 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 同样,继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">RTMFPManager</span> <span class=\"o\">:</span> <span class=\"k\">private</span> <span class=\"n\">Task</span><span class=\"p\">,</span> <span class=\"k\">private</span> <span class=\"n\">Startable</span>\n</code></pre></div></div>\n\n<p>在构造函数中将 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 对象以引用方式传入,用以初始化其 <code class=\"language-plaintext highlighter-rouge\">_server</code> 引用成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">RTMFPManager</span><span class=\"p\">(</span><span class=\"n\">RTMFPServer</span><span class=\"o\">&</span> <span class=\"n\">server</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_server</span><span class=\"p\">(</span><span class=\"n\">server</span><span class=\"p\">),</span>\n <span class=\"n\">Task</span><span class=\"p\">(</span><span class=\"n\">server</span><span class=\"p\">),</span>\n <span class=\"n\">Startable</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPManager\"</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">start</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n\n<span class=\"cm\">/* ...... */</span>\n\n<span class=\"n\">RTMFPServer</span><span class=\"o\">&</span> <span class=\"n\">_server</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">start()</code> 成员函数,是从 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 继承而来的。然后会开启一个新的名为 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的线程。然后响应到 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager::run()</code> 函数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">run</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">setPriority</span><span class=\"p\">(</span><span class=\"n\">Thread</span><span class=\"o\">::</span><span class=\"n\">PRIO_LOW</span><span class=\"p\">);</span>\n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"mi\">2000</span><span class=\"p\">)</span><span class=\"o\">!=</span><span class=\"n\">STOP</span><span class=\"p\">)</span>\n <span class=\"n\">waitHandle</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这里要强调的是,这里的 <code class=\"language-plaintext highlighter-rouge\">setPriority</code> 在 Linux 环境下会设置失败,可以参见我在 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 在 Github 上开启的 Issue #75,其中就包括这里的线程优先级设置。</p>\n\n<p>在这里我们可以看到 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的 <code class=\"language-plaintext highlighter-rouge\">handle(…)</code> 中的 <code class=\"language-plaintext highlighter-rouge\">sleep(…)</code> 是每 2 秒一次,而这是对 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程有影响的。还记得我说的 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程的 <code class=\"language-plaintext highlighter-rouge\">_wakeUpEvent</code> 成员吗?(在第一部分中)它的激活就是在 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 中进行的,所以这里这个 2 秒是会影响到 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 的主循环的等待时间的。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">WakeUpType</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">timeout</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"n\">WakeUpType</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">WAKEUP</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">tryWait</span><span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"p\">))</span>\n <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">TIMEOUT</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">_wakeUpEvent</span><span class=\"p\">.</span><span class=\"n\">wait</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_stop</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">STOP</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>你可以自行修改 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 中 <code class=\"language-plaintext highlighter-rouge\">sleep(...)</code> 的参数,这样就会调用 <code class=\"language-plaintext highlighter-rouge\">_wakeUpEvent.tryWait(timeout)</code> 了,按照指定的等待时间(即 <code class=\"language-plaintext highlighter-rouge\">timeout</code>)来进行睡眠。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 的作用是什么呢?核心就在于它的 <code class=\"language-plaintext highlighter-rouge\">handle</code> 成员函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">handle</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_server</span><span class=\"p\">.</span><span class=\"n\">manage</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这里就会调用到 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::manage()</code>,所以你要在阅读 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 源码时知道 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::manage()</code> 函数并不是在 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程内运行的,而是 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 线程内运行的。它的定义如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">manage</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">manage</span><span class=\"p\">();</span>\n <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">manage</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>它实现对现有 Session 的一些管理,比如终止已经死掉的 <code class=\"language-plaintext highlighter-rouge\">Session</code>。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 8:经由服务器的 Pub/Sub 流程的关键点</title>\n \t<meta name=\"description\" content=\"Flash 客户端通过 NetConnection 与 Cumulus 建立连接,然后通过 NetStream 使用 RTMFP 发布 Audio/Video/Data(下面简称为 A/V/D) 给服务器,这个 Flash Player 就作为一个发布者(Publisher)。RTMFP 服务器接收到后给所有的订阅者(Subscribers)发送 Audio/Video/Data。本文将介绍如何经由服务器实现 Pub/Sub 流程。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 8:经由服务器的 Pub/Sub 流程的关键点</h2>\t\t\n\t<time datetime=\"2012-07-23T03:07:43+00:00\" class=\"by-line\">23 Jul 2012, 广州 | 麦克船长 | 总计 3111 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#1客户端发布publishing-on-client-side\" id=\"markdown-toc-1客户端发布publishing-on-client-side\">1、客户端发布(Publishing on client side)</a></li>\n <li><a href=\"#2服务器端server-side\" id=\"markdown-toc-2服务器端server-side\">2、服务器端(Server-side)</a></li>\n <li><a href=\"#3客户端订阅subscribing-on-client-side\" id=\"markdown-toc-3客户端订阅subscribing-on-client-side\">3、客户端订阅(Subscribing on client side)</a></li>\n <li><a href=\"#4reference\" id=\"markdown-toc-4reference\">4、Reference</a></li>\n</ul>\n\n<p>整个流程概括如下:</p>\n\n<p>Flash 客户端通过 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 与 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 建立连接,然后通过 <code class=\"language-plaintext highlighter-rouge\">NetStream</code> 使用 RTMFP 发布 Audio/Video/Data(下面简称为 A/V/D) 给服务器,这个 Flash Player 就作为一个发布者(Publisher)。RTMFP 服务器接收到后给所有的订阅者(Subscribers)发送 Audio/Video/Data。</p>\n\n<h3 id=\"1客户端发布publishing-on-client-side\">1、客户端发布(Publishing on client side)</h3>\n\n<p>通过 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 连接 RTMFP 服务器 Cumulus,可以参考<a href=\"/2012/04/10/openrtmfp-cumulus-1/\">《OpenRTMFP/Cumulus 原理及源码解读 1:入门介绍、部署与 Hello World》</a>一文。关键的一个语句如下,其中 <code class=\"language-plaintext highlighter-rouge\">nc</code> 是一个 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 对象。</p>\n\n<div class=\"language-actionscript highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">nc</span><span class=\"p\">.</span><span class=\"nx\">connect</span><span class=\"p\">(</span><span class=\"s2\">\"rtmfp://localhost:1935\"</span><span class=\"p\">)</span><span class=\"o\">;</span>\n</code></pre></div></div>\n\n<p>在连接成功后通过 NetStream 发布 Audio/Video,如下所示,其中 <code class=\"language-plaintext highlighter-rouge\">ns1</code> 是一个 <code class=\"language-plaintext highlighter-rouge\">NetStream</code> 对象。</p>\n\n<div class=\"language-actionscript highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">ns1</span><span class=\"p\">.</span><span class=\"nx\">publish</span><span class=\"p\">(</span><span class=\"s2\">\"poechant_media_flow\"</span><span class=\"p\">,</span> <span class=\"s2\">\"live\"</span><span class=\"p\">)</span><span class=\"o\">;</span>\n</code></pre></div></div>\n\n<p>根据音视频不同的需求,播放相应内容。如果是发布 Data,则使用NetStream.send()来实现。这样就完成了客户端的 A/V/D 发布</p>\n\n<h3 id=\"2服务器端server-side\">2、服务器端(Server-side)</h3>\n\n<p>Cumulus 通过 <code class=\"language-plaintext highlighter-rouge\">RTMFPReceiving</code> 这个 RTMFP 协议数据接收引擎完成一些连接建立的相关动作,以及接收数据包:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">receive</span><span class=\"p\">(</span><span class=\"n\">RTMFPReceiving</span><span class=\"o\">&</span> <span class=\"n\">rtmfpReceiving</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>该函数会在收到客户端发来请求时响应,如果是仍未建立连接的请求,则由此创建 Session(RTMFP 的核心概念之一),并取出其中的数据包。这其中有多个过程,我这里就不详述,以后会发布文章来解释。</p>\n\n<p>继续我们的话题,在RTMFPServer::receive 函数中如果是建立连接阶段,则会调用 <code class=\"language-plaintext highlighter-rouge\">Handshake</code> 类的 <code class=\"language-plaintext highlighter-rouge\">receive</code> 来做接下来的处理,这个我就不去详细分析了,因为与本文主题无关。与本文有关的是,如果是已经创建了 Session 的,则会调用:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">ServerSession</span><span class=\"o\">::</span><span class=\"n\">packetHandler</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">packet</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这是一个相对复杂的函数,会从 packet 中取出很多有用的信息。此外,比较重要的是,在我们上述情况下,会调用 Flow 类的:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Flow</span><span class=\"o\">::</span><span class=\"n\">fragmentSortedHandler</span><span class=\"p\">(</span><span class=\"n\">UInt64</span> <span class=\"n\">stage</span><span class=\"p\">,</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">fragment</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">flags</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>该函数中会对 Audio/Video/Data 分别响应不同的处理机制:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">switch</span><span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">AMF_WITH_HANDLER</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">AMF</span><span class=\"p\">:</span>\n <span class=\"n\">messageHandler</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"n\">amf</span><span class=\"p\">);</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">AUDIO</span><span class=\"p\">:</span>\n <span class=\"n\">audioHandler</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">Message</span><span class=\"o\">::</span><span class=\"n\">VIDEO</span><span class=\"p\">:</span>\n <span class=\"n\">videoHandler</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n <span class=\"nl\">default:</span>\n <span class=\"n\">rawHandler</span><span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>接下来在 <code class=\"language-plaintext highlighter-rouge\">Publication</code> 中完成对所有订阅了该发布者的 Flash Players 发送信息,核心的代码为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"n\">it</span> <span class=\"o\">!=</span> <span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span> <span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"o\">-></span><span class=\"n\">pushAudioPacket</span><span class=\"p\">(</span><span class=\"n\">time</span><span class=\"p\">,</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"n\">packet</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n \n<span class=\"k\">for</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"o\">=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span><span class=\"n\">it</span><span class=\"o\">!=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span><span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"o\">-></span><span class=\"n\">pushVideoPacket</span><span class=\"p\">(</span><span class=\"n\">time</span><span class=\"p\">,</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"n\">packet</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n \n<span class=\"k\">for</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"o\">=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span><span class=\"n\">it</span><span class=\"o\">!=</span><span class=\"n\">_listeners</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span><span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"o\">-></span><span class=\"n\">pushDataPacket</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"n\">packet</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>其中的 <code class=\"language-plaintext highlighter-rouge\">_listeners</code> 就是该 <code class=\"language-plaintext highlighter-rouge\">Publication</code> 中的所有订阅者。订阅者的添加/删除是通过:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Listener</span><span class=\"o\">&</span> <span class=\"n\">addListener</span><span class=\"p\">(</span>\n <span class=\"n\">Peer</span><span class=\"o\">&</span> <span class=\"n\">peer</span><span class=\"p\">,</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">id</span><span class=\"p\">,</span>\n <span class=\"n\">FlowWriter</span><span class=\"o\">&</span> <span class=\"n\">writer</span><span class=\"p\">,</span>\n <span class=\"kt\">bool</span> <span class=\"n\">unbuffered</span><span class=\"p\">);</span>\n \n<span class=\"kt\">void</span> <span class=\"nf\">removeListener</span><span class=\"p\">(</span>\n <span class=\"n\">Peer</span><span class=\"o\">&</span> <span class=\"n\">peer</span><span class=\"p\">,</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">id</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这两个函数来实现的。</p>\n\n<p>要注意的是,在 Publication 中已经完成了向订阅者发布信息,之后虽然会响应到 Peer 及 RTMFPServer 的onAudioPacket、onVideoPacket、onDataPacket,但此时都与订阅者接收信息无关了。Cumulus 正是在RTMFPServer::onAudioPacket、RTMFPServer::onVideoPacket、RTMFPServer::onDataPacket中调用用户定制的服务(Lua 脚本实现),完成一些自定义的需求。我是在此通过直接的 C++ 功能扩展,来添加业务需求的,没有使用 Lua 脚本及 Cumulus 中的 Lua 脚本引擎,主要原因是为了提高效率。</p>\n\n<h3 id=\"3客户端订阅subscribing-on-client-side\">3、客户端订阅(Subscribing on client side)</h3>\n\n<p>订阅很简单,在 play 的时候传入正确的发布者名称即可。</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>ns2.play(\"poechant_media_flow\");\n</code></pre></div></div>\n\n<p>测试代码可以参考 Reference-1,其中的例子是关于 <code class=\"language-plaintext highlighter-rouge\">NetStream::send(…)</code> 的,用于发送 <code class=\"language-plaintext highlighter-rouge\">Data</code>,<code class=\"language-plaintext highlighter-rouge\">Audio</code> 和 <code class=\"language-plaintext highlighter-rouge\">Video</code> 的程序可以参考该例修改。</p>\n\n<p>客户端订阅后,这些信息并不会直接从发布者那里通过 P2P 的方式接收。如果想使用发布者与接受者直接连接的方式,则需要在 <code class=\"language-plaintext highlighter-rouge\">NetStream</code> 初始化的时候,传入 <code class=\"language-plaintext highlighter-rouge\">NetStream.DIRECT_CONNECTIONS</code> 参数,默认的 <code class=\"language-plaintext highlighter-rouge\">NetStream.CONNECT_TO_FMS</code> 是将数据上行到服务器再下行给所有订阅者(Subscribers)的。根据不同的应用场景,可以使用不同的方式。</p>\n\n<h3 id=\"4reference\">4、Reference</h3>\n\n<ul>\n <li>http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html#send()</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 7:Cumulus 源码的一个线程启动 Bug 及修复方法</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。Cumulus 启动后,我们可以看到有多个线程被创建,但是有时其中的个别线程没有被成功启动,本文将告诉你如何修复并解决。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 7:Cumulus 源码的一个线程启动 Bug 及修复方法</h2>\t\t\n\t<time datetime=\"2012-06-25T02:56:26+00:00\" class=\"by-line\">25 Jun 2012, 广州 | 麦克船长 | 总计 2111 字</time>\n\t<div class=\"content\">\n\t\t<p><code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 中的线程都是继承自 <code class=\"language-plaintext highlighter-rouge\">Startable</code>,在其中封装 <code class=\"language-plaintext highlighter-rouge\">Poco::Thread</code> 成员,使得一些有关线程的操作更方便。<code class=\"language-plaintext highlighter-rouge\">Startable</code> 中的 <code class=\"language-plaintext highlighter-rouge\">start</code> 函数如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_stop</span><span class=\"p\">)</span> <span class=\"c1\">// if running</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutex</span><span class=\"p\">);</span>\n \n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_haveToJoin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"n\">_haveToJoin</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span>\n <span class=\"s\">\"Try to start up a new thread inherited from Startable\"</span><span class=\"p\">);</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">_process</span><span class=\"p\">);</span>\n <span class=\"n\">_haveToJoin</span> <span class=\"o\">=</span> \n <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutexStop</span><span class=\"p\">);</span>\n <span class=\"n\">_stop</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> \n <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span>\n <span class=\"s\">\"Impossible to start the thread : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这样一个类继承 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 的话,并启动时传入自己,则会调用到 <code class=\"language-plaintext highlighter-rouge\">Startable::start()</code>,然后调用到该类自己的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数。一般来说这个函数会一个循环,以 <code class=\"language-plaintext highlighter-rouge\">SocketManager</code> 为例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">SocketManager</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"err\">…</span> \n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">running</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"err\">…</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>我们要看看这个 <code class=\"language-plaintext highlighter-rouge\">running()</code> 是怎么回事,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">bool</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">running</span><span class=\"p\">()</span> <span class=\"k\">const</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"o\">!</span><span class=\"n\">_stop</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>很简单,就是通过 <code class=\"language-plaintext highlighter-rouge\">Startable::_stop</code> 成员来判断是否还需要继续循环下去。那么这个 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 是什么时候被设置为 <code class=\"language-plaintext highlighter-rouge\">false</code> 的呢?就是上面的 <code class=\"language-plaintext highlighter-rouge\">start()</code>,这里存在的一个问题就是先 <code class=\"language-plaintext highlighter-rouge\">start</code> 线程,再设置 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 为 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>_thread.start(_process);\n_stop=false;\n</code></pre></div></div>\n\n<p>而 <code class=\"language-plaintext highlighter-rouge\">start()</code> 之后 <code class=\"language-plaintext highlighter-rouge\">run()</code> 的时候就开始通过 <code class=\"language-plaintext highlighter-rouge\">running()</code> 来判断 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 值了。所以你会在使用 <code class=\"language-plaintext highlighter-rouge\">Cumulus</code> 时,发现有时候启动起来的线程个数不对。正常情况下应该有四个线程:</p>\n\n<p><img src=\"/img/src/2012-06-25-openrtmfp-cumulus-7-1.png\" alt=\"image\" /></p>\n\n<p>它们是:</p>\n\n<ul>\n <li>主线程</li>\n <li><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 线程</li>\n <li><code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 线程</li>\n <li><code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 线程</li>\n</ul>\n\n<p>而异常情况可能是 <code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 没有启动,甚至 <code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 都没有启动。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 没有启动的情况,这时客户端是无法接入成功的。</p>\n\n<p><img src=\"/img/src/2012-06-25-openrtmfp-cumulus-7-2.png\" alt=\"image\" /></p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">MainSockets</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPManager</code> 都没有启动的情况 T.T</p>\n\n<p><img src=\"/img/src/2012-06-25-openrtmfp-cumulus-7-3.png\" alt=\"image\" /></p>\n\n<p>具体是哪个线程没有启动成功可以通过 GDB 查看。</p>\n\n<p>解决办法就是将 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 的设置操作,在启动线程之前。不过要注意锁要同时移动,并且在产生异常时设置 <code class=\"language-plaintext highlighter-rouge\">_stop</code> 值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_stop</span><span class=\"p\">)</span> <span class=\"c1\">// if running</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutex</span><span class=\"p\">);</span>\n \n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_haveToJoin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"n\">_haveToJoin</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span>\n <span class=\"s\">\"Try to start up a new thread inherited from Startable\"</span><span class=\"p\">);</span>\n <span class=\"p\">{</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutexStop</span><span class=\"p\">);</span>\n <span class=\"n\">_stop</span><span class=\"o\">=</span>\n <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">_process</span><span class=\"p\">);</span>\n <span class=\"n\">_haveToJoin</span> <span class=\"o\">=</span> \n <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> \n <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">{</span>\n <span class=\"n\">ScopedLock</span>\n \n <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutexStop</span><span class=\"p\">);</span>\n <span class=\"n\">_stop</span> <span class=\"o\">=</span> \n <span class=\"nb\">true</span><span class=\"p\">;</span> \n <span class=\"c1\">// June 25th, 2012, Michael@YY</span>\n <span class=\"p\">}</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span>\n <span class=\"s\">\"Impossible to start the thread : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 6:独立使用 CumulusLib 的线程安全 Bug 修复方法</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。对于使用 Cumulus 来做二次开发的技术人员,CumulusLib 是一定会使用到的,但是 CumulusLib 的源码在被单独使用时是存在严重的线程安全 Bug 的,这就是本文诞生的原因。YY 的网页版流媒体技术服务端使用到 CumulusLib 时遇到了这个问题,因此修复了这个 Bug。最终的 Bug 修复很简单,但是要先理解 CumulusLib 整体线程安全问题才能确定解决方案。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 6:独立使用 CumulusLib 的线程安全 Bug 修复方法</h2>\t\t\n\t<time datetime=\"2012-06-07T15:34:18+00:00\" class=\"by-line\">07 Jun 2012, 广州 | 麦克船长 | 总计 1538 字</time>\n\t<div class=\"content\">\n\t\t<p>OpenRTMFP/Cumulus 提供了 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code> 可以供其他 RTMFP 应用使用,而不局限于 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code>。</p>\n\n<p>一般来说,Thread A 会准备好要 <code class=\"language-plaintext highlighter-rouge\">push</code> 的消息,然后 Thread A 向消息队列 <code class=\"language-plaintext highlighter-rouge\">push</code> 消息。</p>\n\n<p>但是 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code> 中实现的,是 Thread A 向消息队列 <code class=\"language-plaintext highlighter-rouge\">push</code> 消息,然后根据这个消息在队列中的指针,再向消息内填写字段。并期望如下:</p>\n\n<p><img src=\"/img/src/2012-06-07-openrtmfp-cumulus-6-1.png\" alt=\"image\" /></p>\n\n<p>由于在 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 中,一个 Client 只在一个线程内被操作,相应的 <code class=\"language-plaintext highlighter-rouge\">FlowWriter</code> 也不会出现跨线程的问题。但是如果单独使用 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code>,如果出现线程通信,并且共享 <code class=\"language-plaintext highlighter-rouge\">FlowWriter</code> 的话,就会共享消息队列,此时可能出现这种情况。</p>\n\n<p><img src=\"/img/src/2012-06-07-openrtmfp-cumulus-6-2.png\" alt=\"image\" /></p>\n\n<p>这就导致了很严重的错误,会使得进程崩溃。修正的方式,可以是将消息完全准备好之后,再放入队列,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">/*\n * author: michael\n * date: June 6th, 2012\n * type: add\n */</span>\n<span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">FlowWriter</span><span class=\"o\">::</span><span class=\"n\">createAMFMessage</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">)</span>\n \n <span class=\"c1\">// signature.empty() means that we are on the flowWriter of FlowNull</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"p\">(</span><span class=\"n\">_closed</span> <span class=\"o\">||</span> <span class=\"n\">signature</span><span class=\"p\">.</span><span class=\"n\">empty</span><span class=\"p\">()</span> <span class=\"o\">||</span> <span class=\"n\">_band</span><span class=\"p\">.</span><span class=\"n\">failed</span><span class=\"p\">()))</span> <span class=\"p\">{</span>\n <span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">pMessage</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"n\">MessageBuffered</span><span class=\"p\">();</span>\n <span class=\"n\">MessageBuffered</span><span class=\"o\">&</span> <span class=\"n\">message</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"n\">writeResponseHeader</span><span class=\"p\">(</span><span class=\"n\">message</span><span class=\"p\">.</span><span class=\"n\">rawWriter</span><span class=\"p\">,</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">pMessage</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">MessageBuffered</span><span class=\"o\">&</span> <span class=\"n\">message</span><span class=\"p\">(</span><span class=\"n\">_MessageNull</span><span class=\"p\">);</span>\n <span class=\"n\">writeResponseHeader</span><span class=\"p\">(</span><span class=\"n\">message</span><span class=\"p\">.</span><span class=\"n\">rawWriter</span><span class=\"p\">,</span><span class=\"n\">name</span><span class=\"p\">,</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">NULL</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>然后再调用时最后再增加 <code class=\"language-plaintext highlighter-rouge\">push</code> 操作:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">/*\n * author: michael\n * date: June 6th, 2012\n * type: add\n */</span>\n<span class=\"kt\">void</span> <span class=\"n\">FlowWriter</span><span class=\"o\">::</span><span class=\"n\">pushAMFMessage</span><span class=\"p\">(</span><span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">pMessage</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pMessage</span> <span class=\"o\">!=</span> <span class=\"nb\">NULL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_messages</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这样就使得消息的数据被写完了,才被放入队列中,如下:</p>\n\n<p><img src=\"/img/src/2012-06-07-openrtmfp-cumulus-6-3.png\" alt=\"image\" /></p>\n\n<p>不过如果考虑线程安全,多个线程对同一个消息队列进行操作时,就要加锁:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">/*\n * author: michael\n * date: June 6th, 2012\n * type: add\n */</span>\n<span class=\"kt\">void</span> <span class=\"n\">FlowWriter</span><span class=\"o\">::</span><span class=\"n\">pushAMFMessage</span><span class=\"p\">(</span><span class=\"n\">MessageBuffered</span><span class=\"o\">*</span> <span class=\"n\">pMessage</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pMessage</span> <span class=\"o\">!=</span> <span class=\"nb\">NULL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Mutex</span><span class=\"o\">::</span><span class=\"n\">ScopedLock</span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">msgQueueMutex</span><span class=\"p\">);</span>\n <span class=\"n\">_messages</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">pMessage</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这样就基本解决了这个线程安全问题。</p>\n\n<p>另外,使用 <code class=\"language-plaintext highlighter-rouge\">CumulusLib</code> 要遵循 GPL 协议,一定不要忘记。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 5:IO 管理源码分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 Cumulus 中 Input/Output 管理的源码分析,包括流缓冲区、IO 流、局部内存片。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 5:IO 管理源码分析</h2>\t\t\n\t<time datetime=\"2012-04-24T03:31:10+00:00\" class=\"by-line\">24 Apr 2012, 广州 | 麦克船长 | 总计 12668 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一流缓冲区\" id=\"markdown-toc-一流缓冲区\">一、流缓冲区</a> <ul>\n <li><a href=\"#1了解-stdstreambuf\" id=\"markdown-toc-1了解-stdstreambuf\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::streambuf</code></a> <ul>\n <li><a href=\"#11单步移动内置指针\" id=\"markdown-toc-11单步移动内置指针\">1.1、单步移动内置指针</a></li>\n <li><a href=\"#12获取-get-指针和-put-指针的位置\" id=\"markdown-toc-12获取-get-指针和-put-指针的位置\">1.2、获取 get 指针和 put 指针的位置</a></li>\n <li><a href=\"#13设置-get-和-put-指针可达区域的上下界\" id=\"markdown-toc-13设置-get-和-put-指针可达区域的上下界\">1.3、设置 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针可达区域的上下界</a></li>\n </ul>\n </li>\n <li><a href=\"#2memorystreambuf\" id=\"markdown-toc-2memorystreambuf\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code></a> <ul>\n <li><a href=\"#21移动内置的-get-和-put-指针\" id=\"markdown-toc-21移动内置的-get-和-put-指针\">2.1、移动内置的 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针:</a></li>\n <li><a href=\"#22获取-get-和-put-指针当前位置\" id=\"markdown-toc-22获取-get-和-put-指针当前位置\">2.2、获取 get 和 put 指针当前位置:</a></li>\n <li><a href=\"#23获取缓冲区的起始位置和大小\" id=\"markdown-toc-23获取缓冲区的起始位置和大小\">2.3、获取缓冲区的起始位置和大小:</a></li>\n <li><a href=\"#24缓冲区的已写字节数\" id=\"markdown-toc-24缓冲区的已写字节数\">2.4、缓冲区的已写字节数</a></li>\n <li><a href=\"#25显式设定-put-和-get-指针位置\" id=\"markdown-toc-25显式设定-put-和-get-指针位置\">2.5、显式设定 <code class=\"language-plaintext highlighter-rouge\">put</code> 和 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针位置</a></li>\n <li><a href=\"#26-修改缓冲区大小\" id=\"markdown-toc-26-修改缓冲区大小\">2.6 修改缓冲区大小</a></li>\n <li><a href=\"#27构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-27构造函数拷贝构造函数和析构函数\">2.7、构造函数、拷贝构造函数和析构函数</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#二io-流\" id=\"markdown-toc-二io-流\">二、IO 流</a> <ul>\n <li><a href=\"#1了解-stdios\" id=\"markdown-toc-1了解-stdios\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::ios</code></a></li>\n <li><a href=\"#2memoryios\" id=\"markdown-toc-2memoryios\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code></a> <ul>\n <li><a href=\"#21构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-21构造函数拷贝构造函数和析构函数\">2.1、构造函数、拷贝构造函数和析构函数</a></li>\n <li><a href=\"#22得到-memorystreambuf-成员的地址\" id=\"markdown-toc-22得到-memorystreambuf-成员的地址\">2.2、得到 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的地址</a></li>\n <li><a href=\"#23当前位置\" id=\"markdown-toc-23当前位置\">2.3、当前位置</a></li>\n <li><a href=\"#24封装-memorystreambuf-成员的一些函数\" id=\"markdown-toc-24封装-memorystreambuf-成员的一些函数\">2.4、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的一些函数</a></li>\n <li><a href=\"#25-缓冲区可读数据的字节数\" id=\"markdown-toc-25-缓冲区可读数据的字节数\">2.5 缓冲区可读数据的字节数</a></li>\n </ul>\n </li>\n <li><a href=\"#3输入流\" id=\"markdown-toc-3输入流\">3、输入流</a></li>\n <li><a href=\"#4输出流\" id=\"markdown-toc-4输出流\">4、输出流</a> <ul>\n <li><a href=\"#41-构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-41-构造函数拷贝构造函数和析构函数\">4.1 构造函数、拷贝构造函数和析构函数</a></li>\n <li><a href=\"#42-读取和设定已写字节数\" id=\"markdown-toc-42-读取和设定已写字节数\">4.2 读取和设定已写字节数</a></li>\n <li><a href=\"#43-当前位置\" id=\"markdown-toc-43-当前位置\">4.3 当前位置</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#三局部内存片\" id=\"markdown-toc-三局部内存片\">三、局部内存片</a> <ul>\n <li><a href=\"#1构造函数\" id=\"markdown-toc-1构造函数\">1、构造函数</a></li>\n <li><a href=\"#2析构函数\" id=\"markdown-toc-2析构函数\">2、析构函数</a></li>\n <li><a href=\"#3缓冲区切割\" id=\"markdown-toc-3缓冲区切割\">3、缓冲区切割</a></li>\n </ul>\n </li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<p>本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 Cumulus 中 Input/Output 管理的源码分析,包括流缓冲区、IO 流、局部内存片。</p>\n\n<h3 id=\"一流缓冲区\">一、流缓冲区</h3>\n\n<p>这段我们主要分析 MemoryStream.h 文件中定义的类。</p>\n\n<h4 id=\"1了解-stdstreambuf\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::streambuf</code></h4>\n\n<p>首先要了解 <code class=\"language-plaintext highlighter-rouge\">streambuf</code> 内置了一个 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针和一个 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针。<code class=\"language-plaintext highlighter-rouge\">streambuf</code> 的所有操作基本都是对这两个指针的操作。其一些成员函数的缩写中的 <code class=\"language-plaintext highlighter-rouge\">g</code> 和 <code class=\"language-plaintext highlighter-rouge\">p</code> 就分别表示 get pointer 和 put pointer。</p>\n\n<h5 id=\"11单步移动内置指针\">1.1、单步移动内置指针</h5>\n\n<p>Increase get pointer: Advances the get pointer by <code class=\"language-plaintext highlighter-rouge\">n</code> positions. The get pointer is the internal pointer that points to the next location in the controlled input sequence.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">gbump</span> <span class=\"p\">(</span> <span class=\"kt\">int</span> <span class=\"n\">n</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>Increase put pointer: Advances the put pointer by <code class=\"language-plaintext highlighter-rouge\">n</code> positions. The put pointer is the internal pointer that points to the next location of the controlled output sequence.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">pbump</span> <span class=\"p\">(</span> <span class=\"kt\">int</span> <span class=\"n\">n</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<h5 id=\"12获取-get-指针和-put-指针的位置\">1.2、获取 get 指针和 put 指针的位置</h5>\n\n<p>Pointer to current position of input sequence: Returns a reference to the current element of the controlled input sequence (i.e., the “get pointer”).</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">char</span> <span class=\"o\">*</span> <span class=\"n\">gptr</span> <span class=\"p\">(</span> <span class=\"p\">)</span> <span class=\"k\">const</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>Pointer to current position of output sequence: Returns a reference to the current element of the output sequence (the put pointer).</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">char</span> <span class=\"o\">*</span> <span class=\"n\">pptr</span> <span class=\"p\">(</span> <span class=\"p\">)</span> <span class=\"k\">const</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<h5 id=\"13设置-get-和-put-指针可达区域的上下界\">1.3、设置 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针可达区域的上下界</h5>\n\n<p>Set input sequence pointers: Sets values for the pointers that define both the boundaries of the accessible part of the controlled input sequence and the get pointer itself.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">setg</span> <span class=\"p\">(</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gbeg</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gnext</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gend</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">gbeg</code>: New value for the pointer to the beginning of the accessible part of the controlled input sequence.\ngnext: New value for the get pointer, which points to the next element within the controlled input sequence where the next input operation shall be performed.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">gend</code>: New value for the end pointer, just past the end of the accessible part of the controlled input sequence.</li>\n <li>Set output sequence pointers: Sets the values that define the boundaries of the accessible part of the controlled output sequence.</li>\n</ul>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">setp</span> <span class=\"p\">(</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pbeg</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pend</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">pbeg</code>: New value for the pointer to the beginning of the accessible part of the controlled output sequenceand put pointer.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">pend</code>: New value for the end pointer, just past the end of the accessible part of the controlled output sequence.</li>\n</ul>\n\n<h4 id=\"2memorystreambuf\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code></h4>\n\n<p>类定义:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryStreamBuf</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">streambuf</span> <span class=\"p\">{</span>\n <span class=\"k\">friend</span> <span class=\"k\">class</span> <span class=\"nc\">ScopedMemoryClip</span><span class=\"p\">;</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">written</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">void</span> <span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">void</span> <span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">);</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">void</span> <span class=\"n\">position</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gCurrent</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pCurrent</span><span class=\"p\">();</span> <span class=\"c1\">// Explaint below</span>\n \n<span class=\"nl\">private:</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">int</span> <span class=\"n\">overflow</span><span class=\"p\">(</span><span class=\"n\">int_type</span> <span class=\"n\">c</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">int</span> <span class=\"n\">underflow</span><span class=\"p\">();</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">int</span> <span class=\"n\">sync</span><span class=\"p\">();</span>\n \n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_written</span><span class=\"p\">;</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n \n <span class=\"n\">MemoryStreamBuf</span><span class=\"p\">();</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"k\">operator</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span><span class=\"p\">);</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 是 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 的友元,其内部有 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 的成员,这里暂且不管。构造函数传入的参数是缓冲区的地址和缓冲区大小(字节数)。拷贝构造函数和析构函数不必赘述。</p>\n\n<h5 id=\"21移动内置的-get-和-put-指针\">2.1、移动内置的 <code class=\"language-plaintext highlighter-rouge\">get</code> 和 <code class=\"language-plaintext highlighter-rouge\">put</code> 指针:</h5>\n\n<p><code class=\"language-plaintext highlighter-rouge\">put</code> 和 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针都移动:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">pbump</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">gbump</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"22获取-get-和-put-指针当前位置\">2.2、获取 get 和 put 指针当前位置:</h5>\n\n<p>封装 <code class=\"language-plaintext highlighter-rouge\">streambuf</code> 的 <code class=\"language-plaintext highlighter-rouge\">gptr</code> 和 <code class=\"language-plaintext highlighter-rouge\">pptr</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">gCurrent</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">gptr</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">pptr</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"23获取缓冲区的起始位置和大小\">2.3、获取缓冲区的起始位置和大小:</h5>\n\n<p>依赖于内置成员变量 pBuffer 和 bufferSize:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">begin</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">size</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"24缓冲区的已写字节数\">2.4、缓冲区的已写字节数</h5>\n\n<p>读取(其中也可能发生设置操作):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt32</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"kt\">int</span> <span class=\"n\">written</span> <span class=\"o\">=</span> <span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"c1\">// 已写字节数</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">written</span> <span class=\"o\"><</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">written</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">written</span> <span class=\"o\">></span> <span class=\"n\">_written</span><span class=\"p\">)</span> <span class=\"c1\">// 保存已写字节数</span>\n <span class=\"n\">_written</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"p\">)</span><span class=\"n\">written</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">_written</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设置:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_written</span><span class=\"o\">=</span><span class=\"n\">size</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"25显式设定-put-和-get-指针位置\">2.5、显式设定 <code class=\"language-plaintext highlighter-rouge\">put</code> 和 <code class=\"language-plaintext highlighter-rouge\">get</code> 指针位置</h5>\n\n<p>设定 put 和 get 指针为以缓冲区首地址为开始偏移量为 pos 的位置:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">position</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n \n <span class=\"c1\">// 保存已写字节数</span>\n <span class=\"n\">written</span><span class=\"p\">();</span> <span class=\"c1\">// Save nb char written</span>\n \n <span class=\"c1\">// 移动 put 指针</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pos</span> <span class=\"o\">></span> <span class=\"n\">_bufferSize</span><span class=\"p\">)</span>\n <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n <span class=\"n\">pbump</span><span class=\"p\">((</span><span class=\"kt\">int</span><span class=\"p\">)</span> <span class=\"n\">pos</span><span class=\"p\">);</span>\n \n <span class=\"c1\">// 移动 get 指针</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">pos</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"26-修改缓冲区大小\">2.6 修改缓冲区大小</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"c1\">// 大小标识</span>\n <span class=\"n\">_bufferSize</span> <span class=\"o\">=</span> <span class=\"n\">newSize</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// gptr 当前位置</span>\n <span class=\"kt\">int</span> <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">gCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pos</span> <span class=\"o\">></span> <span class=\"n\">_bufferSize</span><span class=\"p\">)</span> <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 设置 gptr 可达范围和当前位置</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">pos</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span> \n <span class=\"c1\">// pptr 当前位置</span>\n <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pos</span> <span class=\"o\">></span> <span class=\"n\">_bufferSize</span><span class=\"p\">)</span> <span class=\"n\">pos</span> <span class=\"o\">=</span> <span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 设置 pptr 可达范围和当前位置</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">pbump</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"27构造函数拷贝构造函数和析构函数\">2.7、构造函数、拷贝构造函数和析构函数</h5>\n\n<p>构造函数会设定 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 和 <code class=\"language-plaintext highlighter-rouge\">gptr</code>,并初始化 <code class=\"language-plaintext highlighter-rouge\">pBuffer</code> 和 <code class=\"language-plaintext highlighter-rouge\">bufferSize</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span> <span class=\"n\">_pBuffer</span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">),</span><span class=\"n\">_bufferSize</span><span class=\"p\">(</span><span class=\"n\">bufferSize</span><span class=\"p\">),</span><span class=\"n\">_written</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span><span class=\"p\">,</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>析构函数会拷贝对方的 <code class=\"language-plaintext highlighter-rouge\">pBuffer</code>、<code class=\"language-plaintext highlighter-rouge\">bufferSizse</code>、<code class=\"language-plaintext highlighter-rouge\">_written</code>,并设定 <code class=\"language-plaintext highlighter-rouge\">gptr</code>、<code class=\"language-plaintext highlighter-rouge\">pptr</code>。注意设定 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 时,要分别调用 <code class=\"language-plaintext highlighter-rouge\">setp</code> 和 <code class=\"language-plaintext highlighter-rouge\">pbump</code>,因为 <code class=\"language-plaintext highlighter-rouge\">setp</code> 仅将 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 设定为传入的首个参数值(与可达范围的首地址相同)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span> <span class=\"n\">_pBuffer</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">),</span><span class=\"n\">_bufferSize</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">),</span><span class=\"n\">_written</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_written</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">gCurrent</span><span class=\"p\">(),</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">pbump</span><span class=\"p\">((</span><span class=\"kt\">int</span><span class=\"p\">)(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">pCurrent</span><span class=\"p\">()</span><span class=\"o\">-</span><span class=\"n\">_pBuffer</span><span class=\"p\">));</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>析构函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">::~</span><span class=\"n\">MemoryStreamBuf</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"二io-流\">二、IO 流</h3>\n\n<h4 id=\"1了解-stdios\">1、了解 <code class=\"language-plaintext highlighter-rouge\">std::ios</code></h4>\n\n<p>Initialize object [<code class=\"language-plaintext highlighter-rouge\">protected</code>]: This protected member initializes the values of the stream’s internal flags and member variables.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"nf\">init</span> <span class=\"p\">(</span> <span class=\"n\">streambuf</span><span class=\"o\">*</span> <span class=\"n\">sb</span> <span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>初始化后如下函数的返回值:</p>\n\n<table>\n <thead>\n <tr>\n <th>member function</th>\n <th>value</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>rdbuf()</td>\n <td>sb</td>\n </tr>\n <tr>\n <td>tie()</td>\n <td>0</td>\n </tr>\n <tr>\n <td>rdstate()</td>\n <td>goodbit if sb is not a null pointer, badbit otherwise</td>\n </tr>\n <tr>\n <td>exceptions()</td>\n <td>goodbit</td>\n </tr>\n <tr>\n <td>flags()</td>\n <td>skipws | dec</td>\n </tr>\n <tr>\n <td>width()</td>\n <td>0</td>\n </tr>\n <tr>\n <td>precision()</td>\n <td>6</td>\n </tr>\n <tr>\n <td>fill()</td>\n <td>‘ ’ (whitespace)</td>\n </tr>\n <tr>\n <td>getloc()</td>\n <td>a copy of locale()</td>\n </tr>\n </tbody>\n</table>\n\n<h4 id=\"2memoryios\">2、<code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code></h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code> 封装 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code>,且是 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 和 <code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code>的基类,用以确保流缓冲区和基类的初始化序列的正确性。该类继承自 <code class=\"language-plaintext highlighter-rouge\">std::ios</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryIOS</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"k\">virtual</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">ios</span>\n<span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">MemoryIOS</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryIOS</span><span class=\"p\">();</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">*</span> <span class=\"n\">rdbuf</span><span class=\"p\">();</span>\n <span class=\"k\">virtual</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">()</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"kt\">void</span> <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">);</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n<span class=\"nl\">private:</span>\n <span class=\"n\">MemoryStreamBuf</span> <span class=\"n\">_buf</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h5 id=\"21构造函数拷贝构造函数和析构函数\">2.1、构造函数、拷贝构造函数和析构函数</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span><span class=\"n\">_buf</span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">poco_ios_init</span><span class=\"p\">(</span><span class=\"o\">&</span><span class=\"n\">_buf</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">poco_ios_init</code> 为 <code class=\"language-plaintext highlighter-rouge\">init</code> 的宏定义,用于初始化成员 <code class=\"language-plaintext highlighter-rouge\">_buf</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">MemoryIOS</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span><span class=\"n\">_buf</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_buf</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">poco_ios_init</span><span class=\"p\">(</span><span class=\"o\">&</span><span class=\"n\">_buf</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>拷贝构造函数同构造函数。如下的析构函数不必赘述:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryIOS</span><span class=\"o\">::~</span><span class=\"n\">MemoryIOS</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"22得到-memorystreambuf-成员的地址\">2.2、得到 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的地址</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">*</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">rdbuf</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"o\">&</span><span class=\"n\">_buf</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"23当前位置\">2.3、当前位置</h5>\n\n<p>这是一个纯虚函数,由 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 和 <code class=\"language-plaintext highlighter-rouge\">MemoryOutpuStream</code> 继承时实现:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">virtual</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">()</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<h5 id=\"24封装-memorystreambuf-成员的一些函数\">2.4、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的一些函数</h5>\n\n<p><code class=\"language-plaintext highlighter-rouge\">begin</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">begin</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">resize</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newSize</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">newSize</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">next</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">position</code> 封装为 <code class=\"language-plaintext highlighter-rouge\">reset</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"o\">>=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">position</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"n\">clear</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"25-缓冲区可读数据的字节数\">2.5 缓冲区可读数据的字节数</h5>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt32</span> <span class=\"n\">MemoryIOS</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"kt\">int</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">size</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"p\">(</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">begin</span><span class=\"p\">());</span> <span class=\"c1\">// 缓冲区剩余可读数据字节数</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">result</span> <span class=\"o\"><</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"p\">)</span><span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3输入流\">3、输入流</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryInputStream</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">MemoryIOS</span><span class=\"p\">,</span> <span class=\"k\">public</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">istream</span>\n<span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"c1\">/// Creates a MemoryInputStream for the given memory area,</span>\n <span class=\"c1\">/// ready for reading.</span>\n <span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryInputStream</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">();</span>\n <span class=\"c1\">/// Destroys the MemoryInputStream.</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>构造函数、拷贝构造函数和析构函数也都没什么可说的,初始化 <code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code> 以及 <code class=\"language-plaintext highlighter-rouge\">istream</code>。<code class=\"language-plaintext highlighter-rouge\">istream</code> 是 <code class=\"language-plaintext highlighter-rouge\">iostream</code> 中的 <code class=\"language-plaintext highlighter-rouge\">basic_istream</code> 别名(<code class=\"language-plaintext highlighter-rouge\">typedef</code>)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryInputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span> \n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"k\">const_cast</span><span class=\"o\"><</span><span class=\"kt\">char</span><span class=\"o\">*></span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">),</span> <span class=\"n\">bufferSize</span><span class=\"p\">),</span> <span class=\"n\">istream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">MemoryInputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryInputStream</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">),</span> <span class=\"n\">istream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">MemoryInputStream</span><span class=\"o\">::~</span><span class=\"n\">MemoryInputStream</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>唯一的一个成员函数是 <code class=\"language-plaintext highlighter-rouge\">current</code>,封装了 <code class=\"language-plaintext highlighter-rouge\">MemoryIOS</code> 的 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员的 <code class=\"language-plaintext highlighter-rouge\">gCurrent</code> 函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryInputStream</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">gCurrent</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"4输出流\">4、输出流</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MemoryOutputStream</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">MemoryIOS</span><span class=\"p\">,</span> <span class=\"k\">public</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">ostream</span>\n <span class=\"c1\">/// An input stream for reading from a memory area.</span>\n<span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">);</span>\n <span class=\"c1\">/// Creates a MemoryOutputStream for the given memory area,</span>\n <span class=\"c1\">/// ready for writing.</span>\n <span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryOutputStream</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">();</span>\n <span class=\"c1\">/// Destroys the MemoryInputStream.</span>\n \n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">written</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h5 id=\"41-构造函数拷贝构造函数和析构函数\">4.1 构造函数、拷贝构造函数和析构函数</h5>\n\n<p>如下,不赘述了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">bufferSize</span><span class=\"p\">)</span><span class=\"o\">:</span> \n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">pBuffer</span><span class=\"p\">,</span> <span class=\"n\">bufferSize</span><span class=\"p\">),</span> <span class=\"n\">ostream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n<span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">(</span><span class=\"n\">MemoryOutputStream</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span><span class=\"o\">:</span>\n <span class=\"n\">MemoryIOS</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">),</span> <span class=\"n\">ostream</span><span class=\"p\">(</span><span class=\"n\">rdbuf</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">MemoryOutputStream</span><span class=\"o\">::~</span><span class=\"n\">MemoryOutputStream</span><span class=\"p\">(){</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"42-读取和设定已写字节数\">4.2 读取和设定已写字节数</h5>\n\n<p>读取:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">written</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设定:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"43-当前位置\">4.3 当前位置</h5>\n\n<p>与 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 中的封装类似:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">MemoryOutputStream</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">rdbuf</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">pCurrent</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"三局部内存片\">三、局部内存片</h3>\n\n<p>在第一部分的流缓冲区介绍 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 时,其中有一个名为 <code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 的友元,它就是本文所要介绍的。首先,最重要的是,<code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 中有一个 <code class=\"language-plaintext highlighter-rouge\">MemoryStreamBuf</code> 成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">ScopedMemoryClip</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">ScopedMemoryClip</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">buffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">offset</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">ScopedMemoryClip</span><span class=\"p\">();</span>\n<span class=\"nl\">private:</span>\n <span class=\"kt\">void</span> <span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Int32</span> <span class=\"n\">offset</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_offset</span><span class=\"p\">;</span>\n <span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">_buffer</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"1构造函数\">1、构造函数</h4>\n\n<p>构造函数传入的参数对应的就是 <code class=\"language-plaintext highlighter-rouge\">ScopedMemoryClip</code> 的两个成员值。其中偏移量不能超过 <code class=\"language-plaintext highlighter-rouge\">MemoryStremamBuf</code> 的缓冲区上线值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ScopedMemoryClip</span><span class=\"o\">::</span><span class=\"n\">ScopedMemoryClip</span><span class=\"p\">(</span><span class=\"n\">MemoryStreamBuf</span><span class=\"o\">&</span> <span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">offset</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_offset</span><span class=\"p\">(</span><span class=\"n\">offset</span><span class=\"p\">),</span> <span class=\"n\">_buffer</span><span class=\"p\">(</span><span class=\"n\">buffer</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_offset</span> <span class=\"o\">>=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">)</span>\n <span class=\"n\">_offset</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_offset</span> <span class=\"o\"><</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_offset</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"n\">_offset</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2析构函数\">2、析构函数</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ScopedMemoryClip</span><span class=\"o\">::~</span><span class=\"n\">ScopedMemoryClip</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"p\">(</span><span class=\"n\">Int32</span><span class=\"p\">)</span><span class=\"n\">_offset</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3缓冲区切割\">3、缓冲区切割</h4>\n\n<p>可以看到构造函数和析构函数中都调用了 <code class=\"language-plaintext highlighter-rouge\">clip</code> 函数,该函数切割完缓冲区,形成局部内存片:</p>\n\n<ul>\n <li>如果传入的偏移量参数为正,则仅保留切割之后的后一部分。</li>\n <li>如果传入的参数为负,则相当于向前扩充缓冲区(只发生于析构函数中)。其源码如下。</li>\n</ul>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">ScopedMemoryClip</span><span class=\"o\">::</span><span class=\"n\">clip</span><span class=\"p\">(</span><span class=\"n\">Int32</span> <span class=\"n\">offset</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n \n <span class=\"c1\">// 获取到 gptr</span>\n <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">gpos</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">gCurrent</span><span class=\"p\">();</span>\n \n <span class=\"c1\">// 偏移缓冲区地址,并修改缓冲区大小</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+=</span> <span class=\"n\">offset</span><span class=\"p\">;</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span> <span class=\"o\">-=</span> <span class=\"n\">offset</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// pptr 的位置减去缓冲区新地址,作为 pptr 的新位置</span>\n <span class=\"kt\">int</span> <span class=\"n\">ppos</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">pCurrent</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 设置 gptr 可达区域和位置</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">setg</span><span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">gpos</span><span class=\"p\">,</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n \n <span class=\"c1\">// 设置 pptr 可达区域和位置</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">setp</span><span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span><span class=\"p\">,</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_pBuffer</span> <span class=\"o\">+</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">pbump</span><span class=\"p\">(</span><span class=\"n\">ppos</span><span class=\"p\">);</span>\n \n <span class=\"c1\">// 如果已写数据数小于偏移量,则可以将已写数据数设置为零</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\"><</span> <span class=\"n\">offset</span><span class=\"p\">)</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 如果已写数据数大于等于偏移量,则减去 offset</span>\n <span class=\"k\">else</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">-=</span> <span class=\"n\">offset</span><span class=\"p\">;</span>\n \n <span class=\"c1\">// 若已写字节数大于缓冲区容量,则设定为缓冲区容量</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">></span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">)</span>\n <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_written</span> <span class=\"o\">=</span> <span class=\"n\">_buffer</span><span class=\"p\">.</span><span class=\"n\">_bufferSize</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>http://www.cplusplus.com/reference/iostream/streambuf/gbump/</li>\n <li>http://www.cplusplus.com/reference/iostream/streambuf/pbump/</li>\n <li>http://www.cplusplus.com/reference/iostream/ios/init/</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 4:AMF 解析源码分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 ActionScript 独有的 AMF 数据格式,并对其序列化和反序列化的源码进行详细解读。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 4:AMF 解析源码分析</h2>\t\t\n\t<time datetime=\"2012-04-24T02:04:55+00:00\" class=\"by-line\">24 Apr 2012, 广州 | 麦克船长 | 总计 30820 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一amf-数据类型定义\" id=\"markdown-toc-一amf-数据类型定义\">一、AMF 数据类型定义</a> <ul>\n <li><a href=\"#1数据类型\" id=\"markdown-toc-1数据类型\">1、数据类型</a></li>\n <li><a href=\"#2undefined-type\" id=\"markdown-toc-2undefined-type\">2、<code class=\"language-plaintext highlighter-rouge\">undefined</code> Type</a></li>\n <li><a href=\"#3null-type\" id=\"markdown-toc-3null-type\">3、<code class=\"language-plaintext highlighter-rouge\">null</code> Type</a></li>\n <li><a href=\"#4false-type\" id=\"markdown-toc-4false-type\">4、<code class=\"language-plaintext highlighter-rouge\">false</code> type</a></li>\n <li><a href=\"#5true-type\" id=\"markdown-toc-5true-type\">5、<code class=\"language-plaintext highlighter-rouge\">true</code> type</a></li>\n <li><a href=\"#6integer-type\" id=\"markdown-toc-6integer-type\">6、<code class=\"language-plaintext highlighter-rouge\">integer</code> type</a></li>\n <li><a href=\"#7double-type\" id=\"markdown-toc-7double-type\">7、<code class=\"language-plaintext highlighter-rouge\">double</code> type</a></li>\n <li><a href=\"#8string-type\" id=\"markdown-toc-8string-type\">8、<code class=\"language-plaintext highlighter-rouge\">String</code> type</a></li>\n <li><a href=\"#9xmldocument-type\" id=\"markdown-toc-9xmldocument-type\">9、<code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> type</a></li>\n <li><a href=\"#10date-type\" id=\"markdown-toc-10date-type\">10、<code class=\"language-plaintext highlighter-rouge\">Date</code> type</a></li>\n <li><a href=\"#11array-type\" id=\"markdown-toc-11array-type\">11、<code class=\"language-plaintext highlighter-rouge\">Array</code> type</a></li>\n <li><a href=\"#12object-type\" id=\"markdown-toc-12object-type\">12、<code class=\"language-plaintext highlighter-rouge\">Object</code> type</a></li>\n <li><a href=\"#13xml-type\" id=\"markdown-toc-13xml-type\">13、<code class=\"language-plaintext highlighter-rouge\">XML</code> type</a></li>\n <li><a href=\"#14bytearray-type\" id=\"markdown-toc-14bytearray-type\">14、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> type</a></li>\n <li><a href=\"#15amf3-的使用\" id=\"markdown-toc-15amf3-的使用\">15、AMF3 的使用</a> <ul>\n <li><a href=\"#151netconnection-and-amf-3\" id=\"markdown-toc-151netconnection-and-amf-3\">15.1、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> and AMF 3</a></li>\n <li><a href=\"#152netconnection-in-actionscript-30\" id=\"markdown-toc-152netconnection-in-actionscript-30\">15.2、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> in ActionScript 3.0</a></li>\n <li><a href=\"#153bytearray-idatainput-and-idataoutput\" id=\"markdown-toc-153bytearray-idatainput-and-idataoutput\">15.3、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code>, <code class=\"language-plaintext highlighter-rouge\">IDataInput</code> and <code class=\"language-plaintext highlighter-rouge\">IDataOutput</code></a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#二binaryreaderwriter\" id=\"markdown-toc-二binaryreaderwriter\">二、<code class=\"language-plaintext highlighter-rouge\">BinaryReader/Writer</code></a> <ul>\n <li><a href=\"#1amf3-数据格式基础\" id=\"markdown-toc-1amf3-数据格式基础\">1、AMF3 数据格式基础</a></li>\n <li><a href=\"#2序列化\" id=\"markdown-toc-2序列化\">2、序列化</a></li>\n <li><a href=\"#3反序列化\" id=\"markdown-toc-3反序列化\">3、反序列化</a></li>\n </ul>\n </li>\n <li><a href=\"#三packetreaderwriter\" id=\"markdown-toc-三packetreaderwriter\">三、<code class=\"language-plaintext highlighter-rouge\">PacketReader/Writer</code></a> <ul>\n <li><a href=\"#1packetreader\" id=\"markdown-toc-1packetreader\">1、PacketReader</a> <ul>\n <li><a href=\"#11封装-memoryinputstream\" id=\"markdown-toc-11封装-memoryinputstream\">1.1、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code></a></li>\n <li><a href=\"#12收缩缓冲区\" id=\"markdown-toc-12收缩缓冲区\">1.2、收缩缓冲区</a></li>\n <li><a href=\"#13构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-13构造函数拷贝构造函数和析构函数\">1.3、构造函数、拷贝构造函数和析构函数</a></li>\n </ul>\n </li>\n <li><a href=\"#2packetwriter\" id=\"markdown-toc-2packetwriter\">2、<code class=\"language-plaintext highlighter-rouge\">PacketWriter</code></a> <ul>\n <li><a href=\"#21封装memoryoutputstream\" id=\"markdown-toc-21封装memoryoutputstream\">2.1、封装<code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code></a></li>\n <li><a href=\"#22封装-binarywriter\" id=\"markdown-toc-22封装-binarywriter\">2.2、封装 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code></a></li>\n <li><a href=\"#23构造函数拷贝构造函数和析构函数\" id=\"markdown-toc-23构造函数拷贝构造函数和析构函数\">2.3、构造函数、拷贝构造函数和析构函数</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#四amfreader\" id=\"markdown-toc-四amfreader\">四、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code></a> <ul>\n <li><a href=\"#1objectdef\" id=\"markdown-toc-1objectdef\">1、<code class=\"language-plaintext highlighter-rouge\">ObjectDef</code></a></li>\n <li><a href=\"#2amfreader-定义\" id=\"markdown-toc-2amfreader-定义\">2、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code> 定义</a> <ul>\n <li><a href=\"#21构造函数析构函数\" id=\"markdown-toc-21构造函数析构函数\">2.1、构造函数、析构函数</a></li>\n <li><a href=\"#22简单封装-packetreader-的一些函数\" id=\"markdown-toc-22简单封装-packetreader-的一些函数\">2.2、简单封装 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code> 的一些函数</a></li>\n <li><a href=\"#23设置-gptr-位置\" id=\"markdown-toc-23设置-gptr-位置\">2.3、设置 <code class=\"language-plaintext highlighter-rouge\">gptr</code> 位置</a></li>\n <li><a href=\"#24判断类型\" id=\"markdown-toc-24判断类型\">2.4、判断类型</a></li>\n </ul>\n </li>\n <li><a href=\"#3解析-as3-null\" id=\"markdown-toc-3解析-as3-null\">3、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Null</code></a></li>\n <li><a href=\"#4解析-as3-number\" id=\"markdown-toc-4解析-as3-number\">4、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Number</code></a></li>\n <li><a href=\"#5解析-as3-integer\" id=\"markdown-toc-5解析-as3-integer\">5、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Integer</code></a></li>\n <li><a href=\"#6解析-as3-boolean\" id=\"markdown-toc-6解析-as3-boolean\">6、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Boolean</code></a></li>\n <li><a href=\"#7开始引用与结束引用\" id=\"markdown-toc-7开始引用与结束引用\">7、开始引用与结束引用</a></li>\n <li><a href=\"#8解析-as3-bytearray\" id=\"markdown-toc-8解析-as3-bytearray\">8、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code></a></li>\n <li><a href=\"#9解析-as3-date\" id=\"markdown-toc-9解析-as3-date\">9、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Date</code></a></li>\n <li><a href=\"#10解析-as3-dictionary\" id=\"markdown-toc-10解析-as3-dictionary\">10、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Dictionary</code></a></li>\n </ul>\n </li>\n</ul>\n\n<p>本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的其中一篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本篇文章主要介绍 ActionScript 独有的 AMF 数据格式,并对其序列化和反序列化的源码进行详细解读。</p>\n\n<h3 id=\"一amf-数据类型定义\">一、AMF 数据类型定义</h3>\n\n<h4 id=\"1数据类型\">1、数据类型</h4>\n\n<p>各种数据类型的标示都在 AMF.h 中定义为宏</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">#define AMF_NUMBER 0x00 // 浮点数\n#define AMF_BOOLEAN 0x01 // 布尔型\n#define AMF_STRING 0x02 // 字符串\n#define AMF_BEGIN_OBJECT 0x03 // 对象,开始\n#define AMF_NULL 0x05 // null\n#define AMF_UNDEFINED 0x06\n#define AMF_REFERENCE 0x07\n#define AMF_MIXED_ARRAY 0x08\n#define AMF_END_OBJECT 0x09 // 对象,结束\n#define AMF_BEGIN_TYPED_OBJECT 0x10\n#define AMF_STRICT_ARRAY 0x0A\n#define AMF_DATE 0x0B // 日期\n#define AMF_LONG_STRING 0x0C // 字符串\n#define AMF_UNSUPPORTED 0x0D\n</span> \n<span class=\"cp\">#define AMF_AVMPLUS_OBJECT 0x11\n#define AMF_END 0xFF\n</span> \n<span class=\"cp\">#define AMF3_UNDEFINED 0x00\n#define AMF3_NULL 0x01\n#define AMF3_FALSE 0x02\n#define AMF3_TRUE 0x03\n#define AMF3_INTEGER 0x04\n#define AMF3_NUMBER 0x05\n#define AMF3_STRING 0x06\n#define AMF3_DATE 0x08\n#define AMF3_ARRAY 0x09\n#define AMF3_OBJECT 0x0A\n#define AMF3_BYTEARRAY 0x0C\n#define AMF3_DICTIONARY 0x11\n</span></code></pre></div></div>\n\n<p>并定义了一个枚举类表示数据类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">AMF</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"k\">enum</span> <span class=\"n\">Type</span> <span class=\"p\">{</span>\n <span class=\"n\">Null</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">Boolean</span><span class=\"p\">,</span>\n <span class=\"n\">Integer</span><span class=\"p\">,</span>\n <span class=\"n\">Number</span><span class=\"p\">,</span>\n <span class=\"n\">String</span><span class=\"p\">,</span>\n <span class=\"n\">Date</span><span class=\"p\">,</span>\n <span class=\"n\">Array</span><span class=\"p\">,</span>\n <span class=\"n\">Object</span><span class=\"p\">,</span>\n <span class=\"n\">ByteArray</span><span class=\"p\">,</span>\n <span class=\"n\">Dictionary</span><span class=\"p\">,</span>\n <span class=\"n\">RawObjectContent</span><span class=\"p\">,</span>\n <span class=\"n\">End</span>\n <span class=\"p\">};</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"2undefined-type\">2、<code class=\"language-plaintext highlighter-rouge\">undefined</code> Type</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">undefined</code> 类型由 <code class=\"language-plaintext highlighter-rouge\">undefined</code> 类型标记表示。此值不会编码任何其他信息。</p>\n\n<h4 id=\"3null-type\">3、<code class=\"language-plaintext highlighter-rouge\">null</code> Type</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">null</code> 类型由 <code class=\"language-plaintext highlighter-rouge\">null</code> 类型标记表示。此值不会编码任何其他信息。</p>\n\n<h4 id=\"4false-type\">4、<code class=\"language-plaintext highlighter-rouge\">false</code> type</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">false</code> 类型由 <code class=\"language-plaintext highlighter-rouge\">false</code> 类型标记表示,用于编码布尔值 <code class=\"language-plaintext highlighter-rouge\">false</code>。注意,在 ActionScript 3.0 中,布尔值的原始形式和对象形式不存在。此值不会编码任何其他信息。</p>\n\n<h4 id=\"5true-type\">5、<code class=\"language-plaintext highlighter-rouge\">true</code> type</h4>\n\n<p>true 类型由 true 类型标记表示,用于编码布尔值 true。注意,在 ActionScript 3.0 中,布尔值的原始形式和对象形式不存在。此值不会编码任何其他信息。</p>\n\n<h4 id=\"6integer-type\">6、<code class=\"language-plaintext highlighter-rouge\">integer</code> type</h4>\n\n<p>在 AMF 3 中,整数使用可变长度的无符号 29 位整数进行序列化。ActionScript 3.0 中的整数类型 - 有符号 <code class=\"language-plaintext highlighter-rouge\">int</code> 类型和无符号 <code class=\"language-plaintext highlighter-rouge\">uint</code> 类型 - 也使用 29 位在 AVM+中表示。如果无符号整数 (<code class=\"language-plaintext highlighter-rouge\">uint</code>) 的值大于等于 229 或者如果有符号整数 (<code class=\"language-plaintext highlighter-rouge\">int</code>) 的值大于等于 228,则它将被 AVM+ 表示为 <code class=\"language-plaintext highlighter-rouge\">double</code> 类型,并使用 AMF 3 double 类型进行序列化。</p>\n\n<h4 id=\"7double-type\">7、<code class=\"language-plaintext highlighter-rouge\">double</code> type</h4>\n\n<p>AMF 3 的 <code class=\"language-plaintext highlighter-rouge\">double</code> 类型与 AMF 0 的 <code class=\"language-plaintext highlighter-rouge\">Number</code> 类型编码方式相同。此类型用于编码 ActionScript <code class=\"language-plaintext highlighter-rouge\">Number</code> 或值大于等于 228 的 ActionScript <code class=\"language-plaintext highlighter-rouge\">int</code> 或值大于等于 229 的 ActionScript <code class=\"language-plaintext highlighter-rouge\">uint</code>。编码值始终是网络字节顺序中的 8 字节 IEEE-754 双精度浮点值 (低内存中的符号位)。</p>\n\n<h4 id=\"8string-type\">8、<code class=\"language-plaintext highlighter-rouge\">String</code> type</h4>\n\n<p>ActionScript String 值使用 AMF 3 中的单个 string 类型表示 - AMF 0 中的 <code class=\"language-plaintext highlighter-rouge\">string</code> 和 <code class=\"language-plaintext highlighter-rouge\">long string</code> 类型的概念不再使用。可以使用对隐式字符串引用表中的索引将字符串作为先前发生的字符串的引用发送。字符串使用 UTF-8 编码 - 但是头可以描述字符串文本或字符串引用。空字符串永远不会作为引用发送。</p>\n\n<h4 id=\"9xmldocument-type\">9、<code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> type</h4>\n\n<p>ActionScript 3.0 引入了新的 XML 类型 (参见 3.13),但是旧版的 XMLDocument 类型在语言中被保留为 <code class=\"language-plaintext highlighter-rouge\">flash.xml.XMLDocument</code>。与 AMF 0 类似,<code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> 的结构需要扁平化为字符串表示以进行序列化。与 AMF 中的其他字符串一样,内容使用 UTF-8 编码。XMLDocuments 可以通过使用对隐式对象引用表中的索引作为先前发生的 <code class=\"language-plaintext highlighter-rouge\">XMLDocument</code> 实例的引用发送。</p>\n\n<h4 id=\"10date-type\">10、<code class=\"language-plaintext highlighter-rouge\">Date</code> type</h4>\n\n<p>在 AMF 3 中,ActionScript Date 简单地作为自 1970 年 1 月 1 日午夜 (UTC 时区) 以来的毫秒数进行序列化。不发送本地时区信息。可以使用对隐式对象引用表中的索引将日期作为先前发生的日期实例的引用发送。</p>\n\n<h4 id=\"11array-type\">11、<code class=\"language-plaintext highlighter-rouge\">Array</code> type</h4>\n\n<p>ActionScript 数组的类型和在数组中的位置是基于它们的索引性质描述的。以下表格概述了这些术语的含义:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">strict</code>:仅包含序数(数字)索引</li>\n <li><code class=\"language-plaintext highlighter-rouge\">dense</code>:序数索引从 0 开始,并且在连续索引之间不存在间隙(即,从 0 到数组长度的每一个索引都被定义了)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sparse</code>:包含至少两个索引之间的一个间隙</li>\n <li><code class=\"language-plaintext highlighter-rouge\">associative</code>:包含至少一个非序数(字符串)索引(有时称为 ECMA 数组)</li>\n</ul>\n\n<p>AMF 将数组分为两部分,密集部分和关联部分。关联部分的二进制表示由名称/值对(可能没有)终止的空字符串。密集部分的二进制表示由密集部分的大小(可能为零)以及有序的值列表(可能没有)组成。在 AMF 中写入的顺序是密集部分的大小,一个以空字符串终止的名称/值对列表,然后是大小的值。数组可以通过使用隐式对象引用表的索引作为先前发生的数组的引用来发送。</p>\n\n<h4 id=\"12object-type\">12、<code class=\"language-plaintext highlighter-rouge\">Object</code> type</h4>\n\n<p>AMF 3 中有一种类型用于处理 ActionScript 对象和自定义用户类。使用术语 “traits” 来描述类的定义特征。除了 “anonymous” 对象和 “typed” 对象,ActionScript 3.0 还引入了两个进一步的 traits 来描述如何序列化对象,即 “dynamic” 和 “externalizable”。以下表格概述了这些术语和它们的含义:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">Anonymous</code>:实际的 ActionScript 对象类型的实例或没有注册别名的类的实例(在反序列化时将其视为对象)。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">Typed</code>:具有注册别名的类的实例。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">Dynamic</code>:具有动态特征声明的类定义的实例;可以在运行时动态地从实例中添加和删除公共变量成员。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">Externalizable</code>:实现 flash.utils.IExternalizable 的类的实例,它完全控制其成员的序列化(特征信息中不包含属性名)。</li>\n</ul>\n\n<p>在这些特征之外,对象的特征信息还可能包括在类上定义的一组公共变量和公共可读写属性名称(即不是函数的公共成员)。成员名称的顺序很重要,因为在特征信息之后的成员值将按照完全相同的顺序出现。这些成员被视为密封成员,因为它们是由类型明确定义的。</p>\n\n<p>如果类型是动态的,则在密封成员之后可以包括一个进一步的部分,该部分将动态成员列为名称/值对。当遇到空字符串名称时,继续读取动态成员。</p>\n\n<p>对象可以通过使用隐式对象引用表中的索引来作为先前发生对象的引用。此外,还可以通过使用隐式特征引用表中的索引将特征信息发送为先前发生的一组特征的引用。</p>\n\n<h4 id=\"13xml-type\">13、<code class=\"language-plaintext highlighter-rouge\">XML</code> type</h4>\n\n<p>ActionScript 3.0 引入了一种新的 <code class=\"language-plaintext highlighter-rouge\">XML</code> 类型,支持 E4X 语法。为了序列化,需要将 <code class=\"language-plaintext highlighter-rouge\">XML</code> 类型展平成字符串表示形式。与 AMF 中的其他字符串一样,内容使用 UTF-8 编码。<code class=\"language-plaintext highlighter-rouge\">XML</code> 实例可以通过使用对隐式对象引用表中的索引作为先前发生的 XML 实例的引用发送。请注意,这种编码对 <code class=\"language-plaintext highlighter-rouge\">XML</code> 的使用造成了一些理论限制。每个 UTF-8 编码的 <code class=\"language-plaintext highlighter-rouge\">XML</code> 实例的字节长度最大为 228-1 字节(大约 256 MB)。</p>\n\n<h4 id=\"14bytearray-type\">14、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> type</h4>\n\n<p>用于保存字节数组,即 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code>。AMF 3 使用可变长度编码 29 位整数序列化此类型,其中包括字节长度前缀,然后是 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 的原始字节。<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 实例可以通过使用对隐式对象引用表中的索引作为先前发生的 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 实例的引用发送。</p>\n\n<h4 id=\"15amf3-的使用\">15、AMF3 的使用</h4>\n\n<h5 id=\"151netconnection-and-amf-3\">15.1、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> and AMF 3</h5>\n\n<p>除了序列化 ActionScript 类型外,AMF 还可用于远程服务的异步调用。可使用简单的消息结构将一批请求发送到远程端点。此消息结构的格式为 AMF 0(参见[AMF0])。可以使用特殊的 <code class=\"language-plaintext highlighter-rouge\">avmplus-object-marker</code> 类型将上下文头值或消息正文切换到 AMF 3 编码。</p>\n\n<h5 id=\"152netconnection-in-actionscript-30\">15.2、<code class=\"language-plaintext highlighter-rouge\">NetConnection</code> in ActionScript 3.0</h5>\n\n<p>在 ActionScript 3.0 中,NetConnection 的限定类名是 flash.net.NetConnection。这个类仍然使用响应器来处理远程端点的结果和状态响应,但是现在需要强类型的 Responder 类。完全限定的类名是 flash.net.Responder。除了正常的结果和状态响应之外,NetConnection 还会分发事件,开发人员可以添加监听器。下面是这些事件的概述:</p>\n\n<ul>\n <li>当异常异步抛出时触发,例如来自本机异步代码。</li>\n <li>当输入或输出错误导致网络操作失败时触发。</li>\n <li>当 NetConnection 对象报告其状态或错误条件时触发。</li>\n <li>如果对 NetConnection.call() 的调用尝试连接到调用者安全沙箱外的服务器,则会触发。</li>\n</ul>\n\n<h5 id=\"153bytearray-idatainput-and-idataoutput\">15.3、<code class=\"language-plaintext highlighter-rouge\">ByteArray</code>, <code class=\"language-plaintext highlighter-rouge\">IDataInput</code> and <code class=\"language-plaintext highlighter-rouge\">IDataOutput</code></h5>\n\n<p>ActionScript 3.0 引入了一种新类型,用于支持以字节数组形式处理原始数据,即 <code class=\"language-plaintext highlighter-rouge\">flash.utils.ByteArray</code>。为了协助 ActionScript 对象序列化和复制,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 实现了 <code class=\"language-plaintext highlighter-rouge\">flash.utils.IDataInput</code> 和 <code class=\"language-plaintext highlighter-rouge\">flash.utils.IDataOutput</code>。这些接口指定了帮助将常见类型写入字节流的实用方法。两个感兴趣的方法是 <code class=\"language-plaintext highlighter-rouge\">IDataOutput.writeObject</code> 和 <code class=\"language-plaintext highlighter-rouge\">IDataInput.readObject</code>。这些方法使用 AMF 编码对象。使用的 AMF 版本由 <code class=\"language-plaintext highlighter-rouge\">ByteArray.objectEncoding</code> 方法控制,该方法可以设置为 AMF 3 或 AMF 0。枚举类型 <code class=\"language-plaintext highlighter-rouge\">flash.net.ObjectEncoding</code> 包含 AMF 版本的常量:分别为 <code class=\"language-plaintext highlighter-rouge\">ObjectEncoding.AMF0</code> 和 <code class=\"language-plaintext highlighter-rouge\">ObjectEncoding.AMF3</code>。</p>\n\n<p>请注意,<code class=\"language-plaintext highlighter-rouge\">ByteArray.writeObject</code> 使用一个版本的 AMF 对整个对象进行编码。与 <code class=\"language-plaintext highlighter-rouge\">NetConnection</code> 不同,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 不会从 AMF 0 开始,然后将 <code class=\"language-plaintext highlighter-rouge\">objectEncoding</code> 属性设置为 AMF 3 并切换到 AMF 3。还请注意,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 为每个 <code class=\"language-plaintext highlighter-rouge\">readObject</code> 和 <code class=\"language-plaintext highlighter-rouge\">writeObject</code> 调用使用新的对象、对象特征和字符串的隐式引用表。</p>\n\n<h3 id=\"二binaryreaderwriter\">二、<code class=\"language-plaintext highlighter-rouge\">BinaryReader/Writer</code></h3>\n\n<h4 id=\"1amf3-数据格式基础\">1、AMF3 数据格式基础</h4>\n\n<p>首先介绍一下变长整数(Variable Length Integer),比如 UInt32 如下。</p>\n\n<p><img src=\"/img/src/2012-04-24-openrtmfp-cumulus-4-1.png\" alt=\"image\" /></p>\n\n<p>上图摘自 Adobe AMF3 官方文档,这是一种压缩方式的整数存储,且每一字节都对后面的数据具有预知作用。那么字符串如何处理呢?下面是字符串的处理方式,AMF0 和 AMF3 都才用 UTF-8 编码方式,并做如下压缩处理:</p>\n\n<p><img src=\"/img/src/2012-04-24-openrtmfp-cumulus-4-2.png\" alt=\"image\" /></p>\n\n<p>上图摘自 Adobe AMF3 官方文档。</p>\n\n<h4 id=\"2序列化\">2、序列化</h4>\n\n<p>序列化包括 8 位、16 位、32 位,以及 UTF-8 和 UTF-16(I guess)编码的 String,还有原生数据(Raw Data)、变长无符号整数(Variable Length Unsigned Integer)以及 IP 地址。所谓序列化就是按照指定格式编写各种对象、基础数据类型值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">BinaryWriter</span> <span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">ostream</span><span class=\"o\">&</span> <span class=\"n\">ostr</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">BinaryWriter</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write32</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write7BitValue</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">write7BitLongValue</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt64</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeAddress</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Address</span><span class=\"o\">&</span> <span class=\"n\">address</span><span class=\"p\">,</span><span class=\"kt\">bool</span> <span class=\"n\">publicFlag</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">writeAddress</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Net</span><span class=\"o\">::</span><span class=\"n\">SocketAddress</span><span class=\"o\">&</span> <span class=\"n\">address</span><span class=\"p\">,</span><span class=\"kt\">bool</span> <span class=\"n\">publicFlag</span><span class=\"p\">);</span>\n <span class=\"k\">static</span> <span class=\"n\">BinaryWriter</span> <span class=\"n\">BinaryWriterNull</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>请注意其中名为 <code class=\"language-plaintext highlighter-rouge\">BinaryWriterNull</code> 的成员。构造函数定义为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">ostream</span><span class=\"o\">&</span> <span class=\"n\">ostr</span><span class=\"p\">)</span><span class=\"o\">:</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">ostr</span><span class=\"p\">,</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">NETWORK_BYTE_ORDER</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n\n<span class=\"n\">BinaryWriter</span><span class=\"o\">::~</span><span class=\"n\">BinaryWriter</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">flush</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>其中 <code class=\"language-plaintext highlighter-rouge\">writeRaw</code> 是简单地封装 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryWriter::writeRaw()</code>,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">((</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入整数实现如下,用的是从 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryReader</code> 继承来的重载运算符操作:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span> \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write32</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入字符串:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt16</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入变长整数,这段代码含义也一目了然,就是读取变长无符号 32 位整数、64 位整数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write7BitValue</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">shift</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">Get7BitValueSize</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">)</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">max</span> <span class=\"o\">=</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">21</span><span class=\"p\">)</span> <span class=\"p\">{</span> <span class=\"c1\">// 4 bytes maximum</span>\n <span class=\"n\">shift</span> <span class=\"o\">=</span> <span class=\"mi\">22</span><span class=\"p\">;</span>\n <span class=\"n\">max</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">7</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"mh\">0x80</span> <span class=\"o\">|</span> <span class=\"p\">((</span><span class=\"n\">value</span><span class=\"o\">>></span><span class=\"n\">shift</span><span class=\"p\">)</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">));</span>\n <span class=\"n\">shift</span> <span class=\"o\">-=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">max</span> <span class=\"o\">?</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0xFF</span> <span class=\"o\">:</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write7BitLongValue</span><span class=\"p\">(</span><span class=\"n\">UInt64</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">shift</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">Get7BitValueSize</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">)</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">max</span> <span class=\"o\">=</span> <span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">63</span><span class=\"p\">;</span> <span class=\"c1\">// Can give 10 bytes!</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">max</span><span class=\"p\">)</span>\n <span class=\"o\">++</span><span class=\"n\">shift</span><span class=\"p\">;</span>\n \n <span class=\"k\">while</span><span class=\"p\">(</span><span class=\"n\">shift</span><span class=\"o\">>=</span><span class=\"mi\">7</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"mh\">0x80</span> <span class=\"o\">|</span> <span class=\"p\">((</span><span class=\"n\">value</span><span class=\"o\">>></span><span class=\"n\">shift</span><span class=\"p\">)</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">));</span>\n <span class=\"n\">shift</span> <span class=\"o\">-=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">max</span> <span class=\"o\">?</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0xFF</span> <span class=\"o\">:</span> <span class=\"n\">value</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写入 IP 地址的两个函数暂略。</p>\n\n<h4 id=\"3反序列化\">3、反序列化</h4>\n\n<p>反序列化就是从指定格式的数据中读出各类型的数据值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">BinaryReader</span> <span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">istream</span><span class=\"o\">&</span> <span class=\"n\">istr</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">BinaryReader</span><span class=\"p\">();</span>\n \n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt64</span> <span class=\"n\">read7BitLongValue</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">read7BitEncoded</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">,</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString8</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString16</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">read16</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">read32</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readAddress</span><span class=\"p\">(</span><span class=\"n\">Address</span><span class=\"o\">&</span> <span class=\"n\">address</span><span class=\"p\">);</span>\n \n <span class=\"k\">static</span> <span class=\"n\">BinaryReader</span> <span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<p>构造与析构函数都很简单:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">istream</span><span class=\"o\">&</span> <span class=\"n\">istr</span><span class=\"p\">)</span> <span class=\"o\">:</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">istr</span><span class=\"p\">,</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">NETWORK_BYTE_ORDER</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">BinaryReader</span><span class=\"o\">::~</span><span class=\"n\">BinaryReader</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读取原生数据(Raw Data):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">((</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">,</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">readRaw</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">,</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写整数,用的是 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryWriter</code> 的重载运算符:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt16</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">write32</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\"><<</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读写整数依旧使用从 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryReader</code> 继承来的运算符操作:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt8</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read8</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\">>></span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">UInt16</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read16</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt16</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\">>></span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">UInt32</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read32</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span> <span class=\"o\">>></span> <span class=\"n\">c</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">c</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>写字符串:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString8</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write8</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">UInt16</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n<span class=\"kt\">void</span> <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">writeString16</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">write16</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">());</span>\n <span class=\"n\">writeRaw</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读取变长整数,分别针对 <code class=\"language-plaintext highlighter-rouge\">UInt32</code> 和 <code class=\"language-plaintext highlighter-rouge\">UInt64</code>,要理解 <code class=\"language-plaintext highlighter-rouge\">AMF3</code> 的变长整数才能理解这个:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt32</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read7BitValue</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">while</span> <span class=\"p\">((</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x80</span><span class=\"p\">)</span> <span class=\"o\">&&</span> <span class=\"n\">n</span> <span class=\"o\"><</span> <span class=\"mi\">3</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"p\">(</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"o\">++</span><span class=\"n\">n</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"p\">((</span><span class=\"n\">n</span><span class=\"o\"><</span><span class=\"mi\">3</span><span class=\"p\">)</span> <span class=\"o\">?</span> <span class=\"mi\">7</span> <span class=\"o\">:</span> <span class=\"mi\">8</span><span class=\"p\">);</span> <span class=\"c1\">// Use all 8 bits from the 4th byte</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"n\">b</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">UInt64</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">read7BitLongValue</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"n\">UInt64</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"k\">while</span> <span class=\"p\">((</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x80</span><span class=\"p\">)</span> <span class=\"o\">&&</span> <span class=\"n\">n</span> <span class=\"o\"><</span> <span class=\"mi\">8</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"mi\">7</span><span class=\"p\">;</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"p\">(</span><span class=\"n\">b</span><span class=\"o\">&</span><span class=\"mh\">0x7F</span><span class=\"p\">);</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">read8</span><span class=\"p\">();</span>\n <span class=\"o\">++</span><span class=\"n\">n</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">result</span> <span class=\"o\"><<=</span> <span class=\"p\">((</span><span class=\"n\">n</span><span class=\"o\"><</span><span class=\"mi\">8</span><span class=\"p\">)</span> <span class=\"o\">?</span> <span class=\"mi\">7</span> <span class=\"o\">:</span> <span class=\"mi\">8</span><span class=\"p\">);</span> <span class=\"c1\">// Use all 8 bits from the 4th byte</span>\n <span class=\"n\">result</span> <span class=\"o\">|=</span> <span class=\"n\">b</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"三packetreaderwriter\">三、<code class=\"language-plaintext highlighter-rouge\">PacketReader/Writer</code></h3>\n\n<h4 id=\"1packetreader\">1、PacketReader</h4>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>#define PACKETRECV_SIZE 2048\nclass PacketReader: public BinaryReader {\npublic:\n PacketReader(const Poco::UInt8* buffer,Poco::UInt32 size);\n PacketReader(PacketReader&);\n virtual ~PacketReader();\n const Poco::UInt32 fragments;\n Poco::UInt32 available(); // 可读字节数\n Poco::UInt8* current();\n Poco::UInt32 position(); // 获取当前的相对位置(相对于起始位置的)\n void reset(Poco::UInt32 newPos = 0); // 设定当前位置\n void shrink(Poco::UInt32 rest);\n void next(Poco::UInt32 size);\nprivate:\n MemoryInputStream _memory;\n};\n</code></pre></div></div>\n\n<h6 id=\"11封装-memoryinputstream\">1.1、封装 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code></h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">available</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">current</code>:当前绝对位置(内存地址)</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">position</code>:当前位置(绝对位置)减去缓冲区起始位置</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">position</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">next</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"12收缩缓冲区\">1.2、收缩缓冲区</h6>\n\n<p>封装了 <code class=\"language-plaintext highlighter-rouge\">MemoryInputStream</code> 的 <code class=\"language-plaintext highlighter-rouge\">resize</code>。不过由于前面的 <code class=\"language-plaintext highlighter-rouge\">if</code> 语句影响,传给 resize 的参数一定不会大于缓冲区的当前大小。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">shrink</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">rest</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">rest</span> <span class=\"o\">></span> <span class=\"n\">available</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"rest %u more upper than available %u bytes\"</span><span class=\"p\">,</span><span class=\"n\">rest</span><span class=\"p\">,</span><span class=\"n\">available</span><span class=\"p\">());</span>\n <span class=\"n\">rest</span> <span class=\"o\">=</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">position</span><span class=\"p\">()</span> <span class=\"o\">+</span> <span class=\"n\">rest</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"13构造函数拷贝构造函数和析构函数\">1.3、构造函数、拷贝构造函数和析构函数</h6>\n\n<p>构造函数先调用父类 <code class=\"language-plaintext highlighter-rouge\">BinaryReader</code> 的构造函数,并初始化 <code class=\"language-plaintext highlighter-rouge\">fragments</code> 和 <code class=\"language-plaintext highlighter-rouge\">_memory</code> 输入流的缓冲区。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">PacketReader</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">buffer</span><span class=\"p\">,</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_memory</span><span class=\"p\">((</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">size</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">fragments</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"c1\">// Consctruction by copy</span>\n<span class=\"n\">PacketReader</span><span class=\"o\">::</span><span class=\"n\">PacketReader</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_memory</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryReader</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">fragments</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">fragments</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"n\">PacketReader</span><span class=\"o\">::~</span><span class=\"n\">PacketReader</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2packetwriter\">2、<code class=\"language-plaintext highlighter-rouge\">PacketWriter</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">PacketWriter</span><span class=\"o\">:</span> <span class=\"k\">public</span> <span class=\"n\">BinaryWriter</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">buffer</span><span class=\"p\">,</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"n\">PacketWriter</span><span class=\"o\">&</span><span class=\"p\">);</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">PacketWriter</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">length</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">good</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">clear</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">limit</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">length</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"kt\">void</span> <span class=\"n\">flush</span><span class=\"p\">();</span>\n<span class=\"nl\">private:</span>\n <span class=\"n\">MemoryOutputStream</span> <span class=\"n\">_memory</span><span class=\"p\">;</span>\n <span class=\"n\">PacketWriter</span><span class=\"o\">*</span> <span class=\"n\">_pOther</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_size</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h6 id=\"21封装memoryoutputstream\">2.1、封装<code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code></h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">available</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">good</code>:不过 <code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code> 也是封装的 <code class=\"language-plaintext highlighter-rouge\">std::ostream</code> 的 <code class=\"language-plaintext highlighter-rouge\">good</code> 函数,True if no error flags are set.</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">bool</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">good</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">good</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">written</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">length</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">position</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">position</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">()</span><span class=\"o\">-</span><span class=\"p\">(</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code>:设置缓冲区的指针位置,即 <code class=\"language-plaintext highlighter-rouge\">position</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">newPos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">newPos</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">next</code>:移动缓冲区指针</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">begin</code>:返回缓冲区的起始地址</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">begin</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">clear</code>:其实就是修改 written 和 position,使得指定位置后面的数据在以后写的时候可以被覆盖,并不是真正的清除。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">clear</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">pos</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">pos</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">limit</code>:封装 <code class=\"language-plaintext highlighter-rouge\">MemoryOutputStream</code> 的 <code class=\"language-plaintext highlighter-rouge\">resize</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">limit</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">length</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">length</span> <span class=\"o\">==</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">length</span> <span class=\"o\">=</span> <span class=\"n\">_size</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">length</span> <span class=\"o\">></span> <span class=\"n\">_size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"Limit '%d' more upper than buffer size '%d' bytes\"</span><span class=\"p\">,</span><span class=\"n\">length</span><span class=\"p\">,</span><span class=\"n\">_size</span><span class=\"p\">);</span>\n <span class=\"n\">length</span> <span class=\"o\">=</span> <span class=\"n\">_size</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"n\">length</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"22封装-binarywriter\">2.2、封装 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code></h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">flush</code>:封装 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code> 的 <code class=\"language-plaintext highlighter-rouge\">flush</code>,不过 <code class=\"language-plaintext highlighter-rouge\">BinaryWriter</code> 的 <code class=\"language-plaintext highlighter-rouge\">flush</code> 实际上是从 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryWriter</code> 继承而来的。其作用是「Flushes the underlying stream」。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">flush</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_pOther</span> <span class=\"o\">&&</span> <span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"n\">_pOther</span><span class=\"o\">-></span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">())</span>\n <span class=\"n\">_pOther</span><span class=\"o\">-></span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">.</span><span class=\"n\">written</span><span class=\"p\">());</span>\n <span class=\"n\">BinaryWriter</span><span class=\"o\">::</span><span class=\"n\">flush</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h6 id=\"23构造函数拷贝构造函数和析构函数\">2.3、构造函数、拷贝构造函数和析构函数</h6>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_memory</span><span class=\"p\">((</span><span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">buffer</span><span class=\"p\">,</span> <span class=\"n\">size</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">_pOther</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">_size</span><span class=\"p\">(</span><span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n \n<span class=\"c1\">// Consctruction by copy</span>\n<span class=\"n\">PacketWriter</span><span class=\"o\">::</span><span class=\"n\">PacketWriter</span><span class=\"p\">(</span><span class=\"n\">PacketWriter</span><span class=\"o\">&</span> <span class=\"n\">other</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">_pOther</span><span class=\"p\">(</span><span class=\"o\">&</span><span class=\"n\">other</span><span class=\"p\">),</span>\n <span class=\"n\">_memory</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">BinaryWriter</span><span class=\"p\">(</span><span class=\"n\">_memory</span><span class=\"p\">),</span>\n <span class=\"n\">_size</span><span class=\"p\">(</span><span class=\"n\">other</span><span class=\"p\">.</span><span class=\"n\">_size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>注意析构函数中会进行 <code class=\"language-plaintext highlighter-rouge\">flush</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">PacketWriter</span><span class=\"o\">::~</span><span class=\"n\">PacketWriter</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">flush</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"四amfreader\">四、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code></h3>\n\n<h4 id=\"1objectdef\">1、<code class=\"language-plaintext highlighter-rouge\">ObjectDef</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">ObjectDef</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span> \n <span class=\"n\">ObjectDef</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">amf3</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">arrayType</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">amf3</span><span class=\"p\">(</span><span class=\"n\">amf3</span><span class=\"p\">),</span>\n <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">dynamic</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">externalizable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">count</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">arrayType</span><span class=\"p\">(</span><span class=\"n\">arrayType</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">list</span><span class=\"o\"><</span><span class=\"n\">string</span><span class=\"o\">></span> <span class=\"n\">hardProperties</span><span class=\"p\">;</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">reset</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">dynamic</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">externalizable</span><span class=\"p\">;</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">count</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">arrayType</span><span class=\"p\">;</span>\n <span class=\"k\">const</span> <span class=\"n\">UInt32</span> <span class=\"n\">amf3</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"2amfreader-定义\">2、<code class=\"language-plaintext highlighter-rouge\">AMFReader</code> 定义</h4>\n\n<p>其中 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code> 作为其成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">AMFReader</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">AMFReader</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">reader</span><span class=\"p\">);</span>\n <span class=\"o\">~</span><span class=\"n\">AMFReader</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">readSimpleObject</span><span class=\"p\">(</span><span class=\"n\">AMFSimpleObject</span><span class=\"o\">&</span> <span class=\"n\">object</span><span class=\"p\">);</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">read</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"kt\">double</span> <span class=\"n\">readNumber</span><span class=\"p\">();</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Int32</span> <span class=\"n\">readInteger</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readBoolean</span><span class=\"p\">();</span>\n <span class=\"n\">BinaryReader</span><span class=\"o\">&</span> <span class=\"n\">readByteArray</span><span class=\"p\">(</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">&</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Timestamp</span> <span class=\"n\">readDate</span><span class=\"p\">();</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">readObject</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readArray</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">readDictionary</span><span class=\"p\">(</span><span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">weakKeys</span><span class=\"p\">);</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">readKey</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">readValue</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">readItem</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">);</span>\n <span class=\"n\">BinaryReader</span><span class=\"o\">&</span> <span class=\"n\">readRawObjectContent</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">readNull</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">available</span><span class=\"p\">();</span>\n \n <span class=\"kt\">void</span> <span class=\"n\">startReferencing</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">stopReferencing</span><span class=\"p\">();</span>\n \n <span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">reader</span><span class=\"p\">;</span>\n \n<span class=\"nl\">private:</span>\n <span class=\"kt\">void</span> <span class=\"n\">readString</span><span class=\"p\">(</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"kt\">void</span> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">list</span><span class=\"o\"><</span><span class=\"n\">ObjectDef</span><span class=\"o\">*></span> <span class=\"n\">_objectDefs</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_stringReferences</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_classDefReferences</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_references</span><span class=\"p\">;</span>\n <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span><span class=\"o\">></span> <span class=\"n\">_amf0References</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_amf0Reset</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_reset</span><span class=\"p\">;</span>\n <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt32</span> <span class=\"n\">_amf3</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">_referencing</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h5 id=\"21构造函数析构函数\">2.1、构造函数、析构函数</h5>\n\n<p>参数为 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code>,会初始化一些成员变量。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">AMFReader</span><span class=\"p\">(</span><span class=\"n\">PacketReader</span><span class=\"o\">&</span> <span class=\"n\">reader</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">reader</span><span class=\"p\">(</span><span class=\"n\">reader</span><span class=\"p\">),</span>\n <span class=\"n\">_reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">_amf3</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">_amf0Reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">_referencing</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>析构时,会逐一释放 <code class=\"language-plaintext highlighter-rouge\">_objectDefs</code> 中对象的内存:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">AMFReader</span><span class=\"o\">::~</span><span class=\"n\">AMFReader</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">list</span><span class=\"o\"><</span><span class=\"n\">ObjectDef</span><span class=\"o\">*>::</span><span class=\"n\">iterator</span> <span class=\"n\">it</span><span class=\"p\">;</span>\n <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"n\">it</span><span class=\"o\">!=</span><span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span> <span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span>\n <span class=\"k\">delete</span> <span class=\"o\">*</span><span class=\"n\">it</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"22简单封装-packetreader-的一些函数\">2.2、简单封装 <code class=\"language-plaintext highlighter-rouge\">PacketReader</code> 的一些函数</h5>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code>:操作指针位置</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_reset</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_reset</span><span class=\"p\">);</span>\n <span class=\"n\">_reset</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">available</code>:根据当前缓冲区大小和 <code class=\"language-plaintext highlighter-rouge\">written</code> 计算得到</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">bool</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">current</code>:<code class=\"language-plaintext highlighter-rouge\">gptr</code> 内存地址</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kr\">inline</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">UInt8</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">current</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">return</span> <span class=\"o\">*</span><span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"23设置-gptr-位置\">2.3、设置 <code class=\"language-plaintext highlighter-rouge\">gptr</code> 位置</h5>\n\n<p>其实 <code class=\"language-plaintext highlighter-rouge\">pptr</code> 也被影响了,但是在 <code class=\"language-plaintext highlighter-rouge\">AMFReader</code> 中只用 <code class=\"language-plaintext highlighter-rouge\">gptr</code>。调用构造函数的时候,<code class=\"language-plaintext highlighter-rouge\">reset</code> 被设为 0,其后在每次读取数据的时候都会影响 <code class=\"language-plaintext highlighter-rouge\">reset</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">reset</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_reset</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_reset</span><span class=\"p\">);</span>\n <span class=\"n\">_reset</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h5 id=\"24判断类型\">2.4、判断类型</h5>\n\n<p>分析请看注释:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">followingType</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先 <code class=\"language-plaintext highlighter-rouge\">reset</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span> <span class=\"o\">!=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_amf3</span> <span class=\"o\">=</span> <span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">back</span><span class=\"p\">()</span><span class=\"o\">-></span><span class=\"n\">amf3</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>是 AMF0 类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span>\n <span class=\"n\">_amf3</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>如果没有可读数据了,则返回 AMF::End。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">available</span><span class=\"p\">())</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">End</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>开始读了,先读到的表示 AMF 数据类型。要注意的是调用 current 并不改变指针的位置,所以你会在线面看到调用 next。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt8</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n \n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_amf3</span> <span class=\"o\">&&</span> <span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF_AVMPLUS_OBJECT</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"n\">_amf3</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">available</span><span class=\"p\">())</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">End</span><span class=\"p\">;</span>\n <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">current</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>AMF3 类型</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">switch</span><span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>Undefined 和 null 都当做 null。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF3_UNDEFINED</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_NULL</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>false 和 true 都是 boolean。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF3_FALSE</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_TRUE</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Boolean</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_INTEGER</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Integer</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_NUMBER</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_STRING</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">String</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_DATE</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Date</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_ARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Array</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_DICTIONARY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Dictionary</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_OBJECT</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Object</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF3_BYTEARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">ByteArray</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>落到 default 手里的话,就跳过这个字节,读取下一个。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"nl\">default:</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Unknown AMF3 type %.2x\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"err\">}</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>AMF0 类型</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">switch</span> <span class=\"p\">(</span><span class=\"n\">type</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">undefined</code> 和 <code class=\"language-plaintext highlighter-rouge\">null</code> 都是 <code class=\"language-plaintext highlighter-rouge\">null</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_UNDEFINED</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_NULL</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">;</span>\n \n <span class=\"k\">case</span> <span class=\"n\">AMF_BOOLEAN</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Boolean</span><span class=\"p\">;</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_NUMBER</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">long string</code> 和 <code class=\"language-plaintext highlighter-rouge\">string</code> 都是 <code class=\"language-plaintext highlighter-rouge\">string</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_LONG_STRING</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_STRING</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">String</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">mixed array</code> 和 <code class=\"language-plaintext highlighter-rouge\">strict array</code> 都是 <code class=\"language-plaintext highlighter-rouge\">array</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_MIXED_ARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_STRICT_ARRAY</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Array</span><span class=\"p\">;</span>\n \n <span class=\"k\">case</span> <span class=\"n\">AMF_DATE</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Date</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">begin object</code> 和 <code class=\"language-plaintext highlighter-rouge\">begin typed object</code> 都是 <code class=\"language-plaintext highlighter-rouge\">object</code></p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_BEGIN_OBJECT</span><span class=\"p\">:</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_BEGIN_TYPED_OBJECT</span><span class=\"p\">:</span>\n <span class=\"k\">return</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Object</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是引用,就跳过表示类型值的这个字节。这个先留下来,带我们分析完 readArray 和 readObject 再回头看。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_REFERENCE</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"n\">UInt16</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read16</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">reference</span> <span class=\"o\">></span> <span class=\"n\">_amf0References</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF0 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_amf0Reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_amf0References</span><span class=\"p\">[</span><span class=\"n\">reference</span><span class=\"p\">]);</span>\n <span class=\"k\">return</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果没了,或者不支持,或者都不是,就跳过这个字节,递归继续读取:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">case</span> <span class=\"n\">AMF_END_OBJECT</span><span class=\"p\">:</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF end object type without begin object type before\"</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"k\">case</span> <span class=\"n\">AMF_UNSUPPORTED</span><span class=\"p\">:</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"Unsupported type in AMF format\"</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"k\">default</span><span class=\"o\">:</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Unknown AMF type %.2x\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">)</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nf\">followingType</span><span class=\"p\">();</span>\n <span class=\"err\">}</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">followingType</code> 是这个类的核心,每个具体的数据类型的分析都依赖于它的判断。这些类型的解析,会在下一篇文章中介绍。</p>\n\n<h4 id=\"3解析-as3-null\">3、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Null</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readNull</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先 reset 一下是惯例,就像糗百上的割一样。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span> \n</code></pre></div></div>\n\n<p>AMF 数据类型</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>如果是 <code class=\"language-plaintext highlighter-rouge\">Null</code>,跳过该字节,并返回</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>判断错误</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Null type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"4解析-as3-number\">4、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Number</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">double</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readNumber</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Null</code> 会被悲催的跳过:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>不是 <code class=\"language-plaintext highlighter-rouge\">Number</code> 呀 :(</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Number type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过该字节吧</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>返回吧,返回之前还用到 <code class=\"language-plaintext highlighter-rouge\">Poco::BinaryReader</code> 的运算符,注意 AS3 中的 <code class=\"language-plaintext highlighter-rouge\">Number</code> 就是 C++ 的 <code class=\"language-plaintext highlighter-rouge\">double</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">double</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"5解析-as3-integer\">5、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Integer</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Int32</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readInteger</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">reset</code> 类型:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>Null 的话:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>不是 <code class=\"language-plaintext highlighter-rouge\">Integer</code> 或者 Number 的话。。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Integer</span> <span class=\"o\">&&</span> <span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Integer type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过吧。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>终于是 <code class=\"language-plaintext highlighter-rouge\">Number</code> 了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Number</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kt\">double</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"k\">return</span> <span class=\"p\">(</span><span class=\"n\">Int32</span><span class=\"p\">)</span><span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>读一个变长的 32 位无符号整数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// Forced in AMF3 here!</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">value</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>如果大于 3.5 个字节所能表示的最大无符号整数值(<code class=\"language-plaintext highlighter-rouge\">268435455</code> 是 <code class=\"language-plaintext highlighter-rouge\">0xFFFFFFF</code>),则减去 <code class=\"language-plaintext highlighter-rouge\">0x2FFFFFFF</code>(这还不是太理解,有能解释的朋友给留个言,或者发 email 给我 )</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">value</span> <span class=\"o\">></span> <span class=\"mi\">268435455</span><span class=\"p\">)</span>\n <span class=\"n\">value</span> <span class=\"o\">-=</span> <span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\"><<</span> <span class=\"mi\">29</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">value</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"6解析-as3-boolean\">6、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Boolean</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readBoolean</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>如果是 <code class=\"language-plaintext highlighter-rouge\">Null</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>居然不是 <code class=\"language-plaintext highlighter-rouge\">Boolean</code> 的话。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Boolean</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Boolean type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果是 <code class=\"language-plaintext highlighter-rouge\">AMF3</code> 的话,返回 <code class=\"language-plaintext highlighter-rouge\">true</code> 或者 <code class=\"language-plaintext highlighter-rouge\">false</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read8</span><span class=\"p\">()</span><span class=\"o\">==</span> <span class=\"n\">AMF3_FALSE</span> <span class=\"o\">?</span> <span class=\"nb\">false</span> <span class=\"o\">:</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>不是 <code class=\"language-plaintext highlighter-rouge\">AMF3</code> 就是 <code class=\"language-plaintext highlighter-rouge\">AMF0</code> 喽:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read8</span><span class=\"p\">()</span><span class=\"o\">==</span><span class=\"mh\">0x00</span> <span class=\"o\">?</span> <span class=\"nb\">false</span> <span class=\"o\">:</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"7开始引用与结束引用\">7、开始引用与结束引用</h4>\n\n<p>如下这两个函数会在 <code class=\"language-plaintext highlighter-rouge\">FlowConnection</code> 中调用。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">startReferencing</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_referencing</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n \n<span class=\"kr\">inline</span> <span class=\"kt\">void</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">stopReferencing</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">_referencing</span> <span class=\"o\">=</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"8解析-as3-bytearray\">8、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code></h4>\n\n<p>先回顾一下 AMF3 中的ByteArray 的数据格式:</p>\n\n<p>注意到,首先要读取一个变长无符号 32 位整数,但是最低位是 1,只有 28 位用于表示数据长度。解释完这里,下面的解析过程才好理解。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">BinaryReader</span><span class=\"o\">&</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readByteArray</span><span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Null</code> 就返回 <code class=\"language-plaintext highlighter-rouge\">BinaryReaderNull</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果不是 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code>,也返回 <code class=\"language-plaintext highlighter-rouge\">BinaryReaderNull</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">ByteArray</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF ByteArray type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过这个字节:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>注意 position 返回的是相对位置,与 AS3 中一样。<code class=\"language-plaintext highlighter-rouge\">reference</code> 表示这个地址(简单说,引用就是地址嘛)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>读取一个变长 32 位无符号整数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>最低位是 1 的话,<code class=\"language-plaintext highlighter-rouge\">isInline</code> 是 <code class=\"language-plaintext highlighter-rouge\">true</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">bool</span> <span class=\"n\">isInline</span> <span class=\"o\">=</span> <span class=\"n\">size</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>右移一位,因为那一位是标志位,上面解释过了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">>>=</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果 <code class=\"language-plaintext highlighter-rouge\">isInline</code> 是 <code class=\"language-plaintext highlighter-rouge\">true</code>,表示是 <code class=\"language-plaintext highlighter-rouge\">ByteArray</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isInline</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>如果 <code class=\"language-plaintext highlighter-rouge\">_referencing</code> 为 <code class=\"language-plaintext highlighter-rouge\">true</code> 的话(这是一个 <code class=\"language-plaintext highlighter-rouge\">vector</code>),push back this reference:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_referencing</span><span class=\"p\">)</span>\n <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">reference</span><span class=\"p\">);</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>不符合 ByteArray 的格式定义的话:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">size</span> <span class=\"o\">></span> <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF3 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">BinaryReader</span><span class=\"o\">::</span><span class=\"n\">BinaryReaderNull</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>移动到这个 reference 的位置,<code class=\"language-plaintext highlighter-rouge\">_references[size]</code> 就是这个位置(相对)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_references</span><span class=\"p\">[</span><span class=\"n\">size</span><span class=\"p\">]);</span> <span class=\"c1\">// TODO size 作为索引,还没有完全理解</span>\n</code></pre></div></div>\n\n<p>读取这个 reference 的 size 值给 size对象(注意 size 是这个函数传入的引用参数,其值可以被修改)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">()</span> <span class=\"o\">>></span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>把读取完 ByteArraty 的 PacketReader 返回:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"n\">reader</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>最后强调一点,<code class=\"language-plaintext highlighter-rouge\">ByteArray</code> 的数据段最大长度为 228 -1 字节,约为 256 MB。</p>\n\n<h4 id=\"9解析-as3-date\">9、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Date</code></h4>\n\n<p>先看下 <code class=\"language-plaintext highlighter-rouge\">Date</code> 的数据格式:</p>\n\n<p>下面开始分析:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Timestamp</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readDate</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>惯例:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>Null 的话,就返回当前时间:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">Timestamp</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果不是 Date 类型,也返回当前时间:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Date</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Date type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">Timestamp</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"kt\">double</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是 AMF3:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先读取 flag,最低一位必须是 1,其他位丢到垃圾桶。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">flags</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>当前相对位置。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>是 1 就 push back 到 <code class=\"language-plaintext highlighter-rouge\">_references</code> 里。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"kt\">bool</span> <span class=\"n\">isInline</span> <span class=\"o\">=</span> <span class=\"n\">flags</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isInline</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_referencing</span><span class=\"p\">)</span>\n <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">reference</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>读取一个 double,到 result 里(result 也是 double 类型哦~)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>如果标志位不是 1,麻烦不少哒。。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">flags</span> <span class=\"o\">>>=</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果 flag 超了,就返回当前时间作为时间戳作为 Date。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">flags</span> <span class=\"o\">></span> <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF3 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">Timestamp</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>这段与 ByteArray 那段一样:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_references</span><span class=\"p\">[</span><span class=\"n\">flags</span><span class=\"p\">]);</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>返回喽~</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"nf\">Timestamp</span><span class=\"p\">((</span><span class=\"n\">Timestamp</span><span class=\"o\">::</span><span class=\"n\">TimeVal</span><span class=\"p\">)</span> <span class=\"n\">result</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">);</span>\n <span class=\"err\">}</span>\n <span class=\"n\">reader</span> <span class=\"o\">>></span> <span class=\"n\">result</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>读俩,因为是 double(64 位):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">);</span> <span class=\"c1\">// Timezone, useless</span>\n</code></pre></div></div>\n\n<p>返回喽~</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"nf\">Timestamp</span><span class=\"p\">((</span><span class=\"n\">Timestamp</span><span class=\"o\">::</span><span class=\"n\">TimeVal</span><span class=\"p\">)</span> <span class=\"n\">result</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"10解析-as3-dictionary\">10、解析 AS3 <code class=\"language-plaintext highlighter-rouge\">Dictionary</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">AMFReader</span><span class=\"o\">::</span><span class=\"n\">readDictionary</span><span class=\"p\">(</span><span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">weakKeys</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>下面这段咱就略了。。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">reset</span><span class=\"p\">();</span>\n <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Type</span> <span class=\"n\">type</span> <span class=\"o\">=</span> <span class=\"n\">followingType</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">==</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Null</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">type</span> <span class=\"o\">!=</span> <span class=\"n\">AMF</span><span class=\"o\">::</span><span class=\"n\">Dictionary</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Type %.2x is not a AMF Dictionary type\"</span><span class=\"p\">,</span><span class=\"n\">type</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>跳过 type:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// AMF3</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">next</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span> <span class=\"c1\">// marker</span>\n</code></pre></div></div>\n\n<p>当前相对位置值作为 <code class=\"language-plaintext highlighter-rouge\">reference</code>,再读个 <code class=\"language-plaintext highlighter-rouge\">size</code>,还是最低位必须为 1,不是就返回 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">UInt32</span> <span class=\"n\">reference</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">UInt32</span> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">();</span>\n <span class=\"kt\">bool</span> <span class=\"n\">isInline</span> <span class=\"o\">=</span> <span class=\"n\">size</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n <span class=\"n\">size</span> <span class=\"o\">>>=</span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">isInline</span> <span class=\"o\">&&</span> <span class=\"n\">size</span><span class=\"o\">></span><span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">size</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"AMF3 reference not found\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>下面要调用到 <code class=\"language-plaintext highlighter-rouge\">ObjectRef</code> 构造函数,这里再把其实现拿出来看看,其实主要是初始化了哪些成员。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ObjectDef</span><span class=\"p\">(</span><span class=\"n\">UInt32</span> <span class=\"n\">amf3</span><span class=\"p\">,</span><span class=\"n\">UInt8</span> <span class=\"n\">arrayType</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"o\">:</span> <span class=\"n\">amf3</span><span class=\"p\">(</span><span class=\"n\">amf3</span><span class=\"p\">),</span>\n <span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">dynamic</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">externalizable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">count</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">),</span>\n <span class=\"n\">arrayType</span><span class=\"p\">(</span><span class=\"n\">arrayType</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>可以看到要有一个 amf3,还有 <code class=\"language-plaintext highlighter-rouge\">reset</code> 置为 0,<code class=\"language-plaintext highlighter-rouge\">dynamic</code> 置为 <code class=\"language-plaintext highlighter-rouge\">false</code>,<code class=\"language-plaintext highlighter-rouge\">externalizable</code> 也是 <code class=\"language-plaintext highlighter-rouge\">false</code>,<code class=\"language-plaintext highlighter-rouge\">count</code> 是 0,<code class=\"language-plaintext highlighter-rouge\">arrayType</code> 成员要赋值。</p>\n\n<p>上面是插播哦,下面还要继续哒。创建这么一个对象,注意是 new 出来的,所以我们在《OpenRTMFP/Cumulus Primer(16)AMF解析之AMFReader》一文中提到了 AMFReader 的析构函数中要对 <code class=\"language-plaintext highlighter-rouge\">_objectRef</code> 的每个元素逐一析构的。<code class=\"language-plaintext highlighter-rouge\">arrayType</code> 就设置为 <code class=\"language-plaintext highlighter-rouge\">AMF3_DICTIONARY</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ObjectDef</span><span class=\"o\">*</span> <span class=\"n\">pObjectDef</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nf\">ObjectDef</span><span class=\"p\">(</span><span class=\"n\">_amf3</span><span class=\"p\">,</span> <span class=\"n\">AMF3_DICTIONARY</span><span class=\"p\">);</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">dynamic</span><span class=\"o\">=</span><span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"n\">_objectDefs</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">pObjectDef</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果标志位是 1,就直接 push back,跟之前一样。不过这里多了一个 <code class=\"language-plaintext highlighter-rouge\">pObjectDef</code>,所以还要设置一下它的计数为 <code class=\"language-plaintext highlighter-rouge\">size</code>,就是 <code class=\"language-plaintext highlighter-rouge\">dictionary</code> 数据大小。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isInline</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_referencing</span><span class=\"p\">)</span>\n <span class=\"n\">_references</span><span class=\"p\">.</span><span class=\"n\">push_back</span><span class=\"p\">(</span><span class=\"n\">reference</span><span class=\"p\">);</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">count</span> <span class=\"o\">=</span> <span class=\"n\">size</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果标志位是 0,就把 <code class=\"language-plaintext highlighter-rouge\">count</code> 设置为下一个变长整数值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">reset</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">position</span><span class=\"p\">();</span>\n <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">reset</span><span class=\"p\">(</span><span class=\"n\">_references</span><span class=\"p\">[</span><span class=\"n\">size</span><span class=\"p\">]);</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">count</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read7BitValue</span><span class=\"p\">()</span> <span class=\"o\">>></span> <span class=\"mi\">1</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">pObjectDef</span><span class=\"o\">-></span><span class=\"n\">count</span> <span class=\"o\">*=</span> <span class=\"mi\">2</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>读一个字节,如果最小位是 1,weakKeys 就是 true,否则为 false。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">weakKeys</span> <span class=\"o\">=</span> <span class=\"n\">reader</span><span class=\"p\">.</span><span class=\"n\">read8</span><span class=\"p\">()</span> <span class=\"o\">&</span> <span class=\"mh\">0x01</span><span class=\"p\">;</span>\n \n <span class=\"k\">return</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 3:CumulusServer 源码主进程主循环分析</title>\n \t<meta name=\"description\" content=\"CumulusServer 主进程的主循环分析,看本文一篇就够了。从绑定地址开始,本文介绍了如何接收数据,如何在 CumulusEdge 和 CumulusServer 的 socket 不同情况下的处理逻辑,如何处理发送方 IP 被禁、数据包大小异常等问题。通过本文让你了解 CumulusServer 的主循环,需要你对 POCO 库有一点了解,还要稍微熟悉 C++ 的基本语法。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 3:CumulusServer 源码主进程主循环分析</h2>\t\t\n\t<time datetime=\"2012-04-15T14:26:58+00:00\" class=\"by-line\">15 Apr 2012, 广州 | 麦克船长 | 总计 3844 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#1绑定地址\" id=\"markdown-toc-1绑定地址\">1、绑定地址</a></li>\n <li><a href=\"#2cumulusserver-接收数据\" id=\"markdown-toc-2cumulusserver-接收数据\">2、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 接收数据</a></li>\n <li><a href=\"#3如果-cumulusedge-端口存在且-edge-socket-可用\" id=\"markdown-toc-3如果-cumulusedge-端口存在且-edge-socket-可用\">3、如果 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 端口存在且 edge socket 可用。</a></li>\n <li><a href=\"#4cumulusserver-和-cumulusedge-的-socket-都没有数据\" id=\"markdown-toc-4cumulusserver-和-cumulusedge-的-socket-都没有数据\">4、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 和 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 都没有数据:</a></li>\n <li><a href=\"#5发送方的-ip-被禁\" id=\"markdown-toc-5发送方的-ip-被禁\">5、发送方的 ip 被禁:</a></li>\n <li><a href=\"#6数据包长度小于可能的最小值12\" id=\"markdown-toc-6数据包长度小于可能的最小值12\">6、数据包长度小于可能的最小值(12)</a></li>\n</ul>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 主进程的主循环分析,看本文一篇就够了。从绑定地址开始,本文介绍了如何接收数据,如何在 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 和 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 socket 不同情况下的处理逻辑,如何处理发送方 IP 被禁、数据包大小异常等问题。通过本文让你了解 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的主循环,需要你对 POCO 库有一点了解,还要稍微熟悉 C++ 的基本语法。</p>\n\n<p>本所要介绍的这个主循环在 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run(const volatile bool& terminate)</code> 函数中。RTMFPServer覆盖 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 的 <code class=\"language-plaintext highlighter-rouge\">run(const volatile bool &terminate)</code> 方法。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"k\">volatile</span> <span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<h3 id=\"1绑定地址\">1、绑定地址</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 IP 地址和端口:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SocketAddress</span> <span class=\"nf\">address</span><span class=\"p\">(</span><span class=\"s\">\"0.0.0.0\"</span><span class=\"p\">,</span><span class=\"n\">_port</span><span class=\"p\">);</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">address</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">);</span>\n<span class=\"err\">绑定</span><span class=\"n\">CumulusEdge</span><span class=\"err\">的</span> <span class=\"n\">IP</span> <span class=\"err\">地址和端口:</span>\n\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SocketAddress</span> <span class=\"nf\">edgesAddress</span><span class=\"p\">(</span><span class=\"s\">\"0.0.0.0\"</span><span class=\"p\">,</span><span class=\"n\">_edgesPort</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">edgesAddress</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>发送者(Client)的 IP 地址和端口:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SocketAddress</span> <span class=\"n\">sender</span><span class=\"p\">;</span>\n <span class=\"n\">UInt8</span> <span class=\"n\">buff</span><span class=\"p\">[</span><span class=\"n\">PACKETRECV_SIZE</span><span class=\"p\">];</span>\n <span class=\"kt\">int</span> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n \n <span class=\"k\">while</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"kt\">bool</span> <span class=\"n\">idle</span> <span class=\"o\">=</span> <span class=\"n\">realTime</span><span class=\"p\">(</span><span class=\"n\">stop</span><span class=\"p\">);</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">stop</span><span class=\"p\">)</span>\n <span class=\"k\">break</span><span class=\"p\">;</span>\n \n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">isEdges</span><span class=\"o\">=</span><span class=\"nb\">false</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<h3 id=\"2cumulusserver-接收数据\">2、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 接收数据</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 有数据可读:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>从 <code class=\"language-plaintext highlighter-rouge\">socket</code> 读取:把数据存到 <code class=\"language-plaintext highlighter-rouge\">buff</code>,把发送者地址赋给 <code class=\"language-plaintext highlighter-rouge\">sender</code>,把所读长度返回给 <code class=\"language-plaintext highlighter-rouge\">size</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">receiveFrom</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">,</span><span class=\"k\">sizeof</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">),</span><span class=\"n\">sender</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>处理 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 产生的异常:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span><span class=\"s\">\"Main socket reception : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">address</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">);</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"3如果-cumulusedge-端口存在且-edge-socket-可用\">3、如果 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 端口存在且 edge socket 可用。</h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 有数据可读:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span> <span class=\"o\">></span> <span class=\"mi\">0</span> <span class=\"o\">&&</span> <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">available</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">receiveFrom</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">,</span> <span class=\"k\">sizeof</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">),</span> <span class=\"n\">sender</span><span class=\"p\">);</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">isEdges</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">DEBUG</span><span class=\"p\">(</span><span class=\"s\">\"Main socket reception : %s\"</span><span class=\"p\">,</span> <span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">bind</span><span class=\"p\">(</span><span class=\"n\">edgesAddress</span><span class=\"p\">,</span> <span class=\"nb\">true</span><span class=\"p\">);</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"n\">Edge</span><span class=\"o\">*</span> <span class=\"n\">pEdge</span> <span class=\"o\">=</span> <span class=\"n\">edges</span><span class=\"p\">(</span><span class=\"n\">sender</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pEdge</span><span class=\"p\">)</span>\n <span class=\"n\">pEdge</span><span class=\"o\">-></span><span class=\"n\">update</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<h3 id=\"4cumulusserver-和-cumulusedge-的-socket-都没有数据\">4、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 和 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的 <code class=\"language-plaintext highlighter-rouge\">socket</code> 都没有数据:</h3>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 空闲:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">idle</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>主线程等待一秒。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Thread</span><span class=\"o\">::</span><span class=\"n\">sleep</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">_timeLastManage</span><span class=\"p\">.</span><span class=\"n\">isElapsed</span><span class=\"p\">(</span><span class=\"n\">_freqManage</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>Just middle session</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_middle</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Sessions</span><span class=\"o\">::</span><span class=\"n\">Iterator</span> <span class=\"n\">it</span><span class=\"p\">;</span>\n <span class=\"k\">for</span> <span class=\"p\">(</span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">begin</span><span class=\"p\">();</span> <span class=\"n\">it</span> <span class=\"o\">!=</span> <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">end</span><span class=\"p\">();</span> <span class=\"o\">++</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">Middle</span><span class=\"o\">*</span> <span class=\"n\">pMiddle</span> <span class=\"o\">=</span> <span class=\"k\">dynamic_cast</span><span class=\"o\"><</span><span class=\"n\">Middle</span><span class=\"o\">*></span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"o\">-></span><span class=\"n\">second</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">pMiddle</span><span class=\"p\">)</span>\n <span class=\"n\">pMiddle</span><span class=\"o\">-></span><span class=\"n\">manage</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"p\">}</span>\n <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"n\">_timeLastManage</span><span class=\"p\">.</span><span class=\"n\">update</span><span class=\"p\">();</span>\n <span class=\"n\">manage</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n <span class=\"err\">}</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"5发送方的-ip-被禁\">5、发送方的 ip 被禁:</h3>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">isBanned</span><span class=\"p\">(</span><span class=\"n\">sender</span><span class=\"p\">.</span><span class=\"n\">host</span><span class=\"p\">()))</span> <span class=\"p\">{</span>\n <span class=\"n\">INFO</span><span class=\"p\">(</span><span class=\"s\">\"Data rejected because client %s is banned\"</span><span class=\"p\">,</span>\n <span class=\"n\">sender</span><span class=\"p\">.</span><span class=\"n\">host</span><span class=\"p\">().</span><span class=\"n\">toString</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"6数据包长度小于可能的最小值12\">6、数据包长度小于可能的最小值(12)</h3>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">size</span> <span class=\"o\"><</span> <span class=\"n\">RTMFP_MIN_PACKET_SIZE</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Invalid packet\"</span><span class=\"p\">);</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n \n <span class=\"n\">PacketReader</span> <span class=\"nf\">packet</span><span class=\"p\">(</span><span class=\"n\">buff</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">Session</span><span class=\"o\">*</span> <span class=\"n\">pSession</span> <span class=\"o\">=</span> <span class=\"n\">findSession</span><span class=\"p\">(</span><span class=\"n\">RTMFP</span><span class=\"o\">::</span><span class=\"n\">Unpack</span><span class=\"p\">(</span><span class=\"n\">packet</span><span class=\"p\">));</span>\n \n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">pSession</span><span class=\"p\">)</span>\n <span class=\"k\">continue</span><span class=\"p\">;</span>\n \n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"n\">pSession</span><span class=\"o\">-></span><span class=\"n\">checked</span><span class=\"p\">)</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">commitCookie</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">pSession</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>给 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 或者自己(<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code>)的 <code class=\"language-plaintext highlighter-rouge\">socket</code>:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">pSession</span><span class=\"o\">-></span><span class=\"n\">setEndPoint</span><span class=\"p\">(</span><span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">isEdges</span> <span class=\"o\">?</span> <span class=\"n\">_edgesSocket</span> <span class=\"o\">:</span> <span class=\"n\">_socket</span><span class=\"p\">,</span><span class=\"n\">sender</span><span class=\"p\">);</span>\n <span class=\"n\">pSession</span><span class=\"o\">-></span><span class=\"n\">receive</span><span class=\"p\">(</span><span class=\"n\">packet</span><span class=\"p\">);</span>\n <span class=\"err\">}</span>\n <span class=\"n\">_handshake</span><span class=\"p\">.</span><span class=\"n\">clear</span><span class=\"p\">();</span>\n <span class=\"n\">_sessions</span><span class=\"p\">.</span><span class=\"n\">clear</span><span class=\"p\">();</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_pCirrus</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">delete</span> <span class=\"n\">_pCirrus</span><span class=\"p\">;</span>\n <span class=\"n\">_pCirrus</span> <span class=\"o\">=</span> <span class=\"nb\">NULL</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 2:CumulusServer 源码启动流程分析</title>\n \t<meta name=\"description\" content=\"本文是麦克船长《OpenRTMFP/Cumulus 原理、源码及实践》系列文章的第二篇,相关内容最初首发于 CSDN 的 Poechant 技术博客,后整理于本博客。本文对 CumulusServer 的启动流程进行了详细的源码解读,其中还包括 CumulusServer 如何处理命令行的各个输入选项、各项命令、如何 dump logs、载入配置、处理日志。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 2:CumulusServer 源码启动流程分析</h2>\t\t\n\t<time datetime=\"2012-04-14T11:20:46+00:00\" class=\"by-line\">14 Apr 2012, 广州 | 麦克船长 | 总计 18890 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一cumulus-启动源码分析\" id=\"markdown-toc-一cumulus-启动源码分析\">一、Cumulus 启动源码分析</a> <ul>\n <li><a href=\"#1maincpp-中的-main-函数\" id=\"markdown-toc-1maincpp-中的-main-函数\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数</a></li>\n <li><a href=\"#2maincpp-中的-cumulusserver-构造函数\" id=\"markdown-toc-2maincpp-中的-cumulusserver-构造函数\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer()</code> 构造函数</a></li>\n <li><a href=\"#3maincpp-中的-cumulusserver-的-initialize-成员函数\" id=\"markdown-toc-3maincpp-中的-cumulusserver-的-initialize-成员函数\">3、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 成员函数</a></li>\n <li><a href=\"#4maincpp-中的-cumulusserver-的-main-成员函数\" id=\"markdown-toc-4maincpp-中的-cumulusserver-的-main-成员函数\">4、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 成员函数</a></li>\n <li><a href=\"#5cumulusserver-是-serverapplication-的子类\" id=\"markdown-toc-5cumulusserver-是-serverapplication-的子类\">5、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 是 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的子类</a></li>\n <li><a href=\"#6serverapplication-是-application-的子类\" id=\"markdown-toc-6serverapplication-是-application-的子类\">6、<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 是 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的子类</a></li>\n <li><a href=\"#7反初始化\" id=\"markdown-toc-7反初始化\">7、反初始化</a></li>\n </ul>\n </li>\n <li><a href=\"#二cumulusserver-各项交互功能的源码解读\" id=\"markdown-toc-二cumulusserver-各项交互功能的源码解读\">二、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 各项交互功能的源码解读</a> <ul>\n <li><a href=\"#1命令行选项设定\" id=\"markdown-toc-1命令行选项设定\">1、命令行选项设定</a></li>\n <li><a href=\"#2处理命令行选项\" id=\"markdown-toc-2处理命令行选项\">2、处理命令行选项</a></li>\n <li><a href=\"#6dump-logs\" id=\"markdown-toc-6dump-logs\">6、Dump logs</a></li>\n <li><a href=\"#3停止运行\" id=\"markdown-toc-3停止运行\">3、停止运行</a></li>\n <li><a href=\"#4载入配置\" id=\"markdown-toc-4载入配置\">4、载入配置</a></li>\n <li><a href=\"#5处理日志\" id=\"markdown-toc-5处理日志\">5、处理日志</a></li>\n </ul>\n </li>\n <li><a href=\"#三maincpp-的-main-函数源码分析\" id=\"markdown-toc-三maincpp-的-main-函数源码分析\">三、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数源码分析</a> <ul>\n <li><a href=\"#1maincpp-中的-main-函数中的-server\" id=\"markdown-toc-1maincpp-中的-main-函数中的-server\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数中的 <code class=\"language-plaintext highlighter-rouge\">server</code></a></li>\n <li><a href=\"#2maincpp-中-main-函数的-serverstart\" id=\"markdown-toc-2maincpp-中-main-函数的-serverstart\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数的 <code class=\"language-plaintext highlighter-rouge\">server.start()</code></a></li>\n <li><a href=\"#3回顾一下整个启动流程\" id=\"markdown-toc-3回顾一下整个启动流程\">3、回顾一下整个启动流程</a></li>\n <li><a href=\"#4rtmfpserverprerunstartableprerun-和-rtmfpserverrun-函数源码\" id=\"markdown-toc-4rtmfpserverprerunstartableprerun-和-rtmfpserverrun-函数源码\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer::prerun()</code>、<code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run(...)</code> 函数源码</a></li>\n </ul>\n </li>\n</ul>\n\n<p>本文对 CumulusServer 的启动流程进行了详细的源码解读,其中还包括 CumulusServer 如何处理命令行的各个输入选项、各项命令、如何 dump logs、载入配置、处理日志。首先要知道的是,OpenRTMFP/Cumulus 中使用到的库有 Poco、OpenSSL 和 Lua。</p>\n\n<h3 id=\"一cumulus-启动源码分析\">一、Cumulus 启动源码分析</h3>\n\n<h4 id=\"1maincpp-中的-main-函数\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数</h4>\n\n<p>入口在 <code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">int</span> <span class=\"nf\">main</span><span class=\"p\">(</span><span class=\"kt\">int</span> <span class=\"n\">argc</span><span class=\"p\">,</span> <span class=\"kt\">char</span><span class=\"o\">*</span> <span class=\"n\">argv</span><span class=\"p\">[])</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先检查内存泄露,不过目前这个开发中的项目还没有实现这个功能,只是个空函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">DetectMemoryLeak</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>然后会创建一个匿名的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 对象,并调用其 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数,该函数由 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 从 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 中继承而来,而 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 由是从 <code class=\"language-plaintext highlighter-rouge\">Application</code> 继承而来的。<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 对象调用 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数,实际是 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数,<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数则是调用 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的函数,而该 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数则是先调用 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数,然后调用 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数,然后调用 <code class=\"language-plaintext highlighter-rouge\">uninitialize()</code> 函数。如果 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数被调用时抛出异常,则不会执行 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数,但仍然会执行 <code class=\"language-plaintext highlighter-rouge\">uninitialize()</code> 函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// Runs the application by performing additional initializations</span>\n <span class=\"c1\">// and calling the main() method.</span>\n <span class=\"k\">return</span> <span class=\"nf\">CumulusServer</span><span class=\"p\">().</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">argc</span><span class=\"p\">,</span> <span class=\"n\">argv</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"2maincpp-中的-cumulusserver-构造函数\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer()</code> 构造函数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的构造函数定义为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">CumulusServer</span><span class=\"p\">()</span><span class=\"o\">:</span> <span class=\"n\">_helpRequested</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span> <span class=\"c1\">// 显示帮助信息</span>\n <span class=\"n\">_pCirrus</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">_middle</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">),</span>\n <span class=\"n\">_isInteractive</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">),</span>\n <span class=\"n\">_pLogFile</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3maincpp-中的-cumulusserver-的-initialize-成员函数\">3、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 成员函数</h4>\n\n<p>在执行 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数之前,<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 会启动 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数,传入的参数就是 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 自己,可以猜到 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::Application</code> 的 <code class=\"language-plaintext highlighter-rouge\">run</code> 方法中,调用该函数时的参数是 <code class=\"language-plaintext highlighter-rouge\">this</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">initialize</span><span class=\"p\">(</span><span class=\"n\">Application</span><span class=\"o\">&</span> <span class=\"n\">self</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>调用父函数 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的初始化函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">initialize</span><span class=\"p\">(</span><span class=\"n\">self</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>再继续下面的源码分析之前,先要知道,根据 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::Application</code> ,<code class=\"language-plaintext highlighter-rouge\">Application</code> 类有一些内置的配置属性,如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">application.path</code>: 可执行文件的绝对路径;</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.name</code>: 可执行文件的文件名(含扩展名);</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.baseName</code>: 可执行文件的文件名(不含扩展名)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.dir</code>: 可执行文件的所在目录;</li>\n <li><code class=\"language-plaintext highlighter-rouge\">application.configDir</code>: 配置文件所在目录;</li>\n</ul>\n\n<p>所以下面就读取了可执行文件的所在目录,其中第二个参数表示默认值(即当前目录):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">string</span> <span class=\"n\">dir</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.dir\"</span><span class=\"p\">,</span> <span class=\"s\">\"./\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>然后读取配置文件,目录为上一句所得到的 <code class=\"language-plaintext highlighter-rouge\">dir</code>,文件名(不含扩展名)为 <code class=\"language-plaintext highlighter-rouge\">application.basename</code> 内置配置属性值,其默认值为 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code>,然后加上点和扩展名 <code class=\"language-plaintext highlighter-rouge\">.ini</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">loadConfiguration</span><span class=\"p\">(</span><span class=\"n\">dir</span>\n <span class=\"o\">+</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.baseName\"</span><span class=\"p\">,</span> <span class=\"s\">\"CumulusServer\"</span><span class=\"p\">)</span>\n <span class=\"o\">+</span> <span class=\"s\">\".ini\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这样就加载完配置了。然后查看当前的进程是从命令行运行的(命令行是交互的,所以是 interactive),还是以 daemon 方式运行的,这个函数是ServerApplication的一个成员函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_isInteractive</span> <span class=\"o\">=</span> <span class=\"n\">isInteractive</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>然后获取表示日志文件所在目录的字符串,其中 <code class=\"language-plaintext highlighter-rouge\">logs.directory</code> 是外置配置属性(配置文件中),其默认值为上面获取到的可执行文件路径(一般为当前路径)与 <code class=\"language-plaintext highlighter-rouge\">logs</code> 的组合,即一般为当前目录下的 <code class=\"language-plaintext highlighter-rouge\">logs</code> 目录:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">string</span> <span class=\"nf\">logDir</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"logs.directory\"</span><span class=\"p\">,</span> <span class=\"n\">dir</span> <span class=\"o\">+</span> <span class=\"s\">\"logs\"</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<p>创建日志文件目录:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">File</span><span class=\"p\">(</span><span class=\"n\">logDir</span><span class=\"p\">).</span><span class=\"n\">createDirectory</span><span class=\"p\">();</span>\n\n</code></pre></div></div>\n\n<p>日志文件绝对路径,<code class=\"language-plaintext highlighter-rouge\">logs</code> 为默认的日志文件名(不含扩展名的部分),扩展名用0:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_logPath</span> <span class=\"o\">=</span> <span class=\"n\">logDir</span> <span class=\"o\">+</span> <span class=\"s\">\"/\"</span> <span class=\"o\">+</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"logs.name\"</span><span class=\"p\">,</span> <span class=\"s\">\"log\"</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"s\">\".\"</span><span class=\"p\">;</span>\n <span class=\"n\">_pLogFile</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nf\">File</span><span class=\"p\">(</span><span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"s\">\"0\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>用日志流打开日志文件(方式为追加写入):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">open</span><span class=\"p\">(</span><span class=\"n\">_pLogFile</span><span class=\"o\">-></span><span class=\"n\">path</span><span class=\"p\">(),</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">in</span> <span class=\"o\">|</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">ate</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Logs</code> 是一个方法类(其中的 <code class=\"language-plaintext highlighter-rouge\">public</code> 函数都是静态的),<code class=\"language-plaintext highlighter-rouge\">SetLogger</code> 的作用就是将Logs中的似有静态成员设置为某个 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Logger</code> 对象(由于 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 继承了 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Logger</code>)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetLogger</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">);</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"4maincpp-中的-cumulusserver-的-main-成员函数\">4、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 成员函数</h4>\n\n<p>OpenRTMFP/Cumulus 是一个基于 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::Application</code> 的服务端应用(准确的说是基于 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::ServerApplication</code> 的服务端应用)。如果没有特殊的启动要求,可以调用 <code class=\"language-plaintext highlighter-rouge\">Poco/Application.h</code> 中定义的宏 <code class=\"language-plaintext highlighter-rouge\">POCO_APP_MAIN</code> 来完成初始化、日志和启动(该宏会根据不同的平台启用不同的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数)。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">run()</code> 函数在调用完 <code class=\"language-plaintext highlighter-rouge\">initialize()</code> 函数后,会调用 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数,该 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数的定义在 <code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">int</span> <span class=\"nf\">main</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">vector</span><span class=\"o\"><</span><span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">>&</span> <span class=\"n\">args</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>首先看是否是要求帮助信息,<code class=\"language-plaintext highlighter-rouge\">displayHelp</code> 是借助 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::HelpFormatter</code> 实现的,<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的构造函数会在调用时将 <code class=\"language-plaintext highlighter-rouge\">_helpRequested</code> 设置为 <code class=\"language-plaintext highlighter-rouge\">false</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_helpRequested</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">displayHelp</span><span class=\"p\">();</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果不是,则进入启动状态,首先创建一个 <code class=\"language-plaintext highlighter-rouge\">RTMFPServerParams</code> 对象 <code class=\"language-plaintext highlighter-rouge\">params</code>,用来存储 OpenRTMFP/CumulusServer 的基本配置信息。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">RTMFPServerParams</span> <span class=\"n\">params</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">params</code> 存储 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的端口号和 <code class=\"language-plaintext highlighter-rouge\">CumulusEdge</code> 的端口号:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"port\"</span><span class=\"p\">,</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">port</span><span class=\"p\">);</span>\n <span class=\"n\">UInt16</span> <span class=\"n\">edgesPort</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"edges.port\"</span><span class=\"p\">,</span>\n <span class=\"n\">RTMFP_DEFAULT_PORT</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">);</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getBool</span><span class=\"p\">(</span><span class=\"s\">\"edges.activated\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">edgesPort</span><span class=\"o\">==</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">WARN</span><span class=\"p\">(</span><span class=\"s\">\"edges.port must have a positive value if \\\n edges.activated is true. Server edges is \\\n disactivated.\"</span><span class=\"p\">);</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesPort</span><span class=\"o\">=</span><span class=\"n\">edgesPort</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">_pCirrus</code> 为 <code class=\"language-plaintext highlighter-rouge\">SocketAddress</code> 的成员,是封装IP地址和端口号的对象。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">pCirrus</span> <span class=\"o\">=</span> <span class=\"n\">_pCirrus</span><span class=\"p\">;</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">middle</span> <span class=\"o\">=</span> <span class=\"n\">_middle</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>UDB 所使用的缓冲区大小:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"udpBufferSize\"</span><span class=\"p\">,</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span>\n <span class=\"s\">\"keepAliveServer\"</span><span class=\"p\">,</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"keepAlivePeer\"</span><span class=\"p\">,</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>失败前 CumulusEdge 的尝试次数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesAttemptsBeforeFallback</span> <span class=\"o\">=</span> <span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getInt</span><span class=\"p\">(</span>\n <span class=\"s\">\"edges.attemptsBeforeFallback\"</span><span class=\"p\">,</span>\n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesAttemptsBeforeFallback</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Server</span> <span class=\"nf\">server</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.dir\"</span><span class=\"p\">,</span><span class=\"s\">\"./\"</span><span class=\"p\">),</span>\n <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span><span class=\"n\">config</span><span class=\"p\">());</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">server</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">waitForTerminationRequest()</code> 函数是 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数中必须调用的,意为等待终止运行的操作请求。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// wait for CTRL-C or kill</span>\n <span class=\"n\">waitForTerminationRequest</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>一旦接收到终止操作的请求,就会执行下面这句,用以退出 OpenRTMFP/Cumulus 的运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"c1\">// Stop the server</span>\n <span class=\"n\">server</span><span class=\"p\">.</span><span class=\"n\">stop</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">catch</code> 一些可能产生的异常:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"Configuration problem : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"CumulusServer : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">what</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(...)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"CumulusServer unknown error\"</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n <span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>OpenRTMFP/CumulusServer 停止运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">return</span> <span class=\"n\">Application</span><span class=\"o\">::</span><span class=\"n\">EXIT_OK</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"5cumulusserver-是-serverapplication-的子类\">5、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 是 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的子类</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 对其子类有如下要求:</p>\n\n<ul>\n <li>Subsystems must be registered in the constructor.</li>\n <li>All non-trivial initializations must be made in the <code class=\"language-plaintext highlighter-rouge\">initialize()</code>` method.</li>\n <li>At the end of the <code class=\"language-plaintext highlighter-rouge\">main()</code> method, <code class=\"language-plaintext highlighter-rouge\">waitForTerminationRequest()</code> should be called.</li>\n</ul>\n\n<h4 id=\"6serverapplication-是-application-的子类\">6、<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 是 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的子类</h4>\n\n<p>Application 对其子类的要求是,如下这些成员函数必须被覆盖:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">initialize()</code> (the one-argument, protected variant):上一篇已介绍过。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">uninitialize()</code>:下面会介绍,Application 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数会在调用 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数后调用 <code class=\"language-plaintext highlighter-rouge\">uninitialize()</code> 函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">reinitialize()</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">defineOptions()</code>:定义命令行启动选项。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">handleOption()</code>:响应相应的命令行选项。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">main()</code></li>\n</ul>\n\n<h4 id=\"7反初始化\">7、反初始化</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 是继承 <code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 的,<code class=\"language-plaintext highlighter-rouge\">ServerApplication</code> 是继承 <code class=\"language-plaintext highlighter-rouge\">Application</code> 的。<code class=\"language-plaintext highlighter-rouge\">Application</code> 的 <code class=\"language-plaintext highlighter-rouge\">run()</code> 函数会先调用 <code class=\"language-plaintext highlighter-rouge\">initialize()</code>,然后调用 <code class=\"language-plaintext highlighter-rouge\">main()</code>,最后调用 <code class=\"language-plaintext highlighter-rouge\">uninitialize</code>。最后这个反初始化过程,在 <code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 就是直接调用父类的 <code class=\"language-plaintext highlighter-rouge\">uninitialize</code> 函数。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">uninitialize</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">uninitialize</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"二cumulusserver-各项交互功能的源码解读\">二、<code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 各项交互功能的源码解读</h3>\n\n<h4 id=\"1命令行选项设定\">1、命令行选项设定</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 的命令行选项有:<code class=\"language-plaintext highlighter-rouge\">log(l)</code>、<code class=\"language-plaintext highlighter-rouge\">dump(d)</code>、<code class=\"language-plaintext highlighter-rouge\">cirrus(c)</code>、<code class=\"language-plaintext highlighter-rouge\">middle(m)</code>、<code class=\"language-plaintext highlighter-rouge\">help(h)</code>。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">defineOptions</span><span class=\"p\">(</span><span class=\"n\">OptionSet</span><span class=\"o\">&</span> <span class=\"n\">options</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">defineOptions</span><span class=\"p\">(</span><span class=\"n\">options</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>设定日志级别(0 - 8,默认是 6,表示 info 级别)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"log\"</span><span class=\"p\">,</span> <span class=\"s\">\"l\"</span><span class=\"p\">,</span> <span class=\"s\">\"Log level argument, must be beetween 0 and 8 : \\\n nothing, fatal, critic, error, warn, note, info, debug, trace. \\\n Default value is 6 (info), all logs until info level are displayed.\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">required</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">argument</span><span class=\"p\">(</span><span class=\"s\">\"level\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<p>其他一些选项:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"dump\"</span><span class=\"p\">,</span> <span class=\"s\">\"d\"</span><span class=\"p\">,</span> <span class=\"s\">\"Enables packet traces in logs. Optional arguments \\\n are 'middle' or 'all' respectively to displays just middle packet \\\n process or all packet process. If no argument is given, just outside \\\n packet process will be dumped.\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">,</span><span class=\"s\">\"middle|all\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"cirrus\"</span><span class=\"p\">,</span> <span class=\"s\">\"c\"</span><span class=\"p\">,</span> <span class=\"s\">\"Cirrus address to activate a 'man-in-the-middle' \\\n developer mode in bypassing flash packets to the official cirrus \\\n server of your choice, it's a instable mode to help Cumulus developers, \\\n </span><span class=\"se\">\\\"</span><span class=\"s\">p2p.rtmfp.net:10000</span><span class=\"se\">\\\"</span><span class=\"s\"> for example. By adding the 'dump' argument, \\\n you will able to display Cirrus/Flash packet exchange in your logs \\\n (see 'dump' argument).\"</span><span class=\"p\">,</span><span class=\"nb\">false</span><span class=\"p\">,</span><span class=\"s\">\"address\"</span><span class=\"p\">,</span><span class=\"nb\">true</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"middle\"</span><span class=\"p\">,</span> <span class=\"s\">\"m\"</span><span class=\"p\">,</span><span class=\"s\">\"Enables a 'man-in-the-middle' developer mode \\\n between two peers. It's a instable mode to help Cumulus developers. \\\n By adding the 'dump' argument, you will able to display Flash/Flash \\\n packet exchange in your logs (see 'dump' argument).\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n</code></pre></div></div>\n\n<p>显示帮助信息的选项:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">options</span><span class=\"p\">.</span><span class=\"n\">addOption</span><span class=\"p\">(</span>\n <span class=\"n\">Option</span><span class=\"p\">(</span><span class=\"s\">\"help\"</span><span class=\"p\">,</span> <span class=\"s\">\"h\"</span><span class=\"p\">,</span> <span class=\"s\">\"Displays help information about command-line usage.\"</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">required</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"n\">repeatable</span><span class=\"p\">(</span><span class=\"nb\">false</span><span class=\"p\">));</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">OptionSet</code> 是 <code class=\"language-plaintext highlighter-rouge\">Poco::Util::OptionSet</code>,调用addOption可以向其中增加选项Option。其中required和repeatable表示:</p>\n\n<p>Sets whether the option is required (flag == true) or optional (flag == false).\nReturns true if the option can be specified more than once, or false if at most once.\n当需要显示帮助信息时,调用如下函数:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">displayHelp</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">HelpFormatter</span> <span class=\"n\">helpFormatter</span><span class=\"p\">(</span><span class=\"n\">options</span><span class=\"p\">());</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">setCommand</span><span class=\"p\">(</span><span class=\"n\">commandName</span><span class=\"p\">());</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">setUsage</span><span class=\"p\">(</span><span class=\"s\">\"OPTIONS\"</span><span class=\"p\">);</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">setHeader</span><span class=\"p\">(</span><span class=\"s\">\"CumulusServer, open source RTMFP server\"</span><span class=\"p\">);</span>\n <span class=\"n\">helpFormatter</span><span class=\"p\">.</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">cout</span><span class=\"p\">);</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">setCommand()</code>: Sets the command name.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">setUsage()</code>: Sets the usage string.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">setHeader()</code>: Sets the header string.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">format()</code>: Writes the formatted help text to the given stream.</li>\n</ul>\n\n<h4 id=\"2处理命令行选项\">2、处理命令行选项</h4>\n\n<p>参数是选项名和选项值。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">handleOption</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">name</span><span class=\"p\">,</span> <span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">value</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">handleOption</span><span class=\"p\">(</span><span class=\"n\">name</span><span class=\"p\">,</span> <span class=\"n\">value</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果选项是帮助:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"help\"</span><span class=\"p\">)</span>\n <span class=\"n\">_helpRequested</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是cirrus,即该服务的 IP 和端口号,Poco::URI 中有协议名(Scheme)、IP 地址(Host)、端口号(Port)、查询串(Query)等等。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"cirrus\"</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">URI</span> <span class=\"n\">uri</span><span class=\"p\">(</span><span class=\"s\">\"rtmfp://\"</span><span class=\"o\">+</span><span class=\"n\">value</span><span class=\"p\">);</span>\n <span class=\"n\">_pCirrus</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"n\">SocketAddress</span><span class=\"p\">(</span><span class=\"n\">uri</span><span class=\"p\">.</span><span class=\"n\">getHost</span><span class=\"p\">(),</span><span class=\"n\">uri</span><span class=\"p\">.</span><span class=\"n\">getPort</span><span class=\"p\">());</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"Mode 'man in the middle' : the exchange will bypass to '%s'\"</span><span class=\"p\">,</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"Mode 'man in the middle' error : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">message</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果选项是dump日志:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"dump\"</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">value</span> <span class=\"o\">==</span> <span class=\"s\">\"all\"</span><span class=\"p\">)</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetDump</span><span class=\"p\">(</span><span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">ALL</span><span class=\"p\">);</span>\n <span class=\"k\">else</span> <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">value</span> <span class=\"o\">==</span> <span class=\"s\">\"middle\"</span><span class=\"p\">)</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetDump</span><span class=\"p\">(</span><span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">MIDDLE</span><span class=\"p\">);</span>\n <span class=\"k\">else</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetDump</span><span class=\"p\">(</span><span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">EXTERNAL</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果选项是middle:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">else</span> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"middle\"</span><span class=\"p\">)</span>\n <span class=\"n\">_middle</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果选项是log,表示设定日志级别:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">else</span> <span class=\"nf\">if</span> <span class=\"p\">(</span><span class=\"n\">name</span> <span class=\"o\">==</span> <span class=\"s\">\"log\"</span><span class=\"p\">)</span>\n <span class=\"n\">Logs</span><span class=\"o\">::</span><span class=\"n\">SetLevel</span><span class=\"p\">(</span><span class=\"n\">atoi</span><span class=\"p\">(</span><span class=\"n\">value</span><span class=\"p\">.</span><span class=\"n\">c_str</span><span class=\"p\">()));</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"6dump-logs\">6、Dump logs</h4>\n\n<p>先加一个作用域锁,然后再向日志流写数据。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">dumpHandler</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">UInt8</span><span class=\"o\">*</span> <span class=\"n\">data</span><span class=\"p\">,</span><span class=\"n\">UInt32</span> <span class=\"n\">size</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ScopedLock</span><span class=\"o\"><</span><span class=\"n\">FastMutex</span><span class=\"o\">></span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_logMutex</span><span class=\"p\">);</span>\n <span class=\"n\">cout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">((</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">((</span><span class=\"k\">const</span> <span class=\"kt\">char</span><span class=\"o\">*</span><span class=\"p\">)</span><span class=\"n\">data</span><span class=\"p\">,</span><span class=\"n\">size</span><span class=\"p\">);</span>\n <span class=\"n\">manageLogFile</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>调用 manageLogFile,主要做一些日志大小超出限制的处理。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">manageLogFile</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>先判断是否超过日志文件的大小上线,LOG_SIZE是1000000字节(即约 1 MB)。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_pLogFile</span><span class=\"o\">-></span><span class=\"n\">getSize</span><span class=\"p\">()</span> <span class=\"o\">></span> <span class=\"n\">LOG_SIZE</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">();</span>\n <span class=\"kt\">int</span> <span class=\"n\">num</span> <span class=\"o\">=</span> <span class=\"mi\">10</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>打开新日志文件:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">File</span> <span class=\"nf\">file</span><span class=\"p\">(</span><span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"s\">\"10\"</span><span class=\"p\">);</span>\n\n</code></pre></div></div>\n\n<p>如果该文件已经存在,则先删除:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">())</span>\n <span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">remove</span><span class=\"p\">();</span>\n\n <span class=\"k\">while</span> <span class=\"p\">(</span><span class=\"o\">--</span><span class=\"n\">num</span> <span class=\"o\">>=</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">file</span> <span class=\"o\">=</span> <span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"n\">NumberFormatter</span><span class=\"o\">::</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">num</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">())</span>\n <span class=\"n\">file</span><span class=\"p\">.</span><span class=\"n\">renameTo</span><span class=\"p\">(</span><span class=\"n\">_logPath</span> <span class=\"o\">+</span> <span class=\"n\">NumberFormatter</span><span class=\"o\">::</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">num</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">));</span>\n <span class=\"p\">}</span>\n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">open</span><span class=\"p\">(</span><span class=\"n\">_pLogFile</span><span class=\"o\">-></span><span class=\"n\">path</span><span class=\"p\">(),</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">in</span> <span class=\"o\">|</span> <span class=\"n\">ios</span><span class=\"o\">::</span><span class=\"n\">ate</span><span class=\"p\">);</span>\n <span class=\"err\">}</span> \n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3停止运行\">3、停止运行</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">CumulusServer</code> 继承了 <code class=\"language-plaintext highlighter-rouge\">ApplicationKiller</code>,该类中有纯虚函数 <code class=\"language-plaintext highlighter-rouge\">kill()</code> 需要被实现,于是有:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">kill</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">terminate</span><span class=\"p\">();</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">ApplicationKiller</code> 的定义在 <code class=\"language-plaintext highlighter-rouge\">ApplicationKiller.h</code> 中,如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">ApplicationKiller</span> <span class=\"p\">{</span>\n<span class=\"nl\">public:</span>\n <span class=\"n\">ApplicationKiller</span><span class=\"p\">(){}</span>\n <span class=\"k\">virtual</span> <span class=\"o\">~</span><span class=\"n\">ApplicationKiller</span><span class=\"p\">(){}</span>\n \n <span class=\"k\">virtual</span> <span class=\"kt\">void</span> <span class=\"n\">kill</span><span class=\"p\">()</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">;</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n\n<h4 id=\"4载入配置\">4、载入配置</h4>\n\n<p>在initialize()函数中调用,上一篇已提到过。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">loadConfiguration</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">path</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">ServerApplication</span><span class=\"o\">::</span><span class=\"n\">loadConfiguration</span><span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">);</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span><span class=\"p\">(...)</span> <span class=\"p\">{</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"5处理日志\">5、处理日志</h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"nf\">logHandler</span><span class=\"p\">(</span><span class=\"n\">Thread</span><span class=\"o\">::</span><span class=\"n\">TID</span> <span class=\"n\">threadId</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">threadName</span><span class=\"p\">,</span>\n <span class=\"n\">Priority</span> <span class=\"n\">priority</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"kt\">char</span> <span class=\"o\">*</span><span class=\"n\">filePath</span><span class=\"p\">,</span>\n <span class=\"kt\">long</span> <span class=\"n\">line</span><span class=\"p\">,</span> \n <span class=\"k\">const</span> <span class=\"kt\">char</span> <span class=\"o\">*</span><span class=\"n\">text</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>作用域锁:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ScopedLock</span><span class=\"o\"><</span><span class=\"n\">FastMutex</span><span class=\"o\">></span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_logMutex</span><span class=\"p\">);</span>\n \n <span class=\"n\">Path</span> <span class=\"nf\">path</span><span class=\"p\">(</span><span class=\"n\">filePath</span><span class=\"p\">);</span>\n <span class=\"n\">string</span> <span class=\"n\">file</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">getExtension</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"s\">\"lua\"</span><span class=\"p\">)</span>\n <span class=\"n\">file</span> <span class=\"o\">+=</span> <span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">directory</span><span class=\"p\">(</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">depth</span><span class=\"p\">()</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"s\">\"/\"</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果是命令行交互模式(即不是 daemon 模式):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_isInteractive</span><span class=\"p\">)</span>\n <span class=\"n\">printf</span><span class=\"p\">(</span><span class=\"s\">\"%s %s[%ld] %s</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">,</span>\n <span class=\"n\">g_logPriorities</span><span class=\"p\">[</span><span class=\"n\">priority</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">],</span>\n <span class=\"p\">(</span><span class=\"n\">file</span> <span class=\"o\">+</span> <span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">getBaseName</span><span class=\"p\">()).</span><span class=\"n\">c_str</span><span class=\"p\">(),</span>\n <span class=\"n\">line</span><span class=\"p\">,</span>\n <span class=\"n\">text</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>向日志流输出一句日志:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_logStream</span> <span class=\"o\"><<</span> <span class=\"n\">DateTimeFormatter</span><span class=\"o\">::</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">LocalDateTime</span><span class=\"p\">(),</span><span class=\"s\">\"%d/%m %H:%M:%S.%c \"</span><span class=\"p\">)</span>\n <span class=\"o\"><<</span> <span class=\"n\">g_logPriorities</span><span class=\"p\">[</span><span class=\"n\">priority</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> \n <span class=\"o\"><<</span> <span class=\"sc\">'\\t'</span> <span class=\"o\"><<</span> <span class=\"n\">threadName</span> \n <span class=\"o\"><<</span> <span class=\"sc\">'('</span> <span class=\"o\"><<</span> <span class=\"n\">threadId</span> <span class=\"o\"><<</span> <span class=\"s\">\")</span><span class=\"se\">\\t</span><span class=\"s\">\"</span>\n <span class=\"o\"><<</span> <span class=\"p\">(</span><span class=\"n\">file</span> <span class=\"o\">+</span> <span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">getFileName</span><span class=\"p\">())</span> \n <span class=\"o\"><<</span> <span class=\"sc\">'['</span> <span class=\"o\"><<</span> <span class=\"n\">line</span> <span class=\"o\"><<</span> <span class=\"s\">\"] \"</span> \n <span class=\"o\"><<</span> <span class=\"n\">text</span> <span class=\"o\"><<</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">endl</span><span class=\"p\">;</span>\n \n <span class=\"n\">_logStream</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>日志文件的善后处理(主要处理文件大小限制可能产生的问题):</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">manageLogFile</span><span class=\"p\">();</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h3 id=\"三maincpp-的-main-函数源码分析\">三、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数源码分析</h3>\n\n<h4 id=\"1maincpp-中的-main-函数中的-server\">1、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中的 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数中的 <code class=\"language-plaintext highlighter-rouge\">server</code></h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中真正启动的是 <code class=\"language-plaintext highlighter-rouge\">server</code>,它继承自 <code class=\"language-plaintext highlighter-rouge\">Cumulus::RTMFPServer</code>,而 <code class=\"language-plaintext highlighter-rouge\">Cumulus::RTMFPServer</code> 又继承自 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Startable</code>、<code class=\"language-plaintext highlighter-rouge\">Cumulus::Gateway</code>、<code class=\"language-plaintext highlighter-rouge\">Cumulus::Handler</code>。而 <code class=\"language-plaintext highlighter-rouge\">Cumulus::Startable</code> 继承自 <code class=\"language-plaintext highlighter-rouge\">Poco::Runnable</code>,所以其是一个可以运行的线程。在 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusServer</code> 中,这是主线程。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Server</span> <span class=\"nf\">server</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"p\">().</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"application.dir\"</span><span class=\"p\">,</span> <span class=\"s\">\"./\"</span><span class=\"p\">),</span> <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">,</span> <span class=\"n\">config</span><span class=\"p\">());</span>\n<span class=\"n\">server</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>这是 <code class=\"language-plaintext highlighter-rouge\">CumulusServer/Server.h</code> 中定义的,其构造函数的原型为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Server</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">root</span><span class=\"p\">,</span>\n <span class=\"n\">ApplicationKiller</span><span class=\"o\">&</span> <span class=\"n\">applicationKiller</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"n\">Poco</span><span class=\"o\">::</span><span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">AbstractConfiguration</span><span class=\"o\">&</span> <span class=\"n\">configurations</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>个参数含义如下:</p>\n\n<blockquote>\n <p>The Path Root for the Server Application Killer for Termanting the Server Application Server Configuration</p>\n</blockquote>\n\n<p>距离来说,在我的 Worksapce 中:</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">root</code> 是 <code class=\"language-plaintext highlighter-rouge\">/Users/michael/Development/workspace/eclipse/OpenRTMFP-Cumulus/Debug/</code> 构造函数的初始化列表极长:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Server</span><span class=\"o\">::</span><span class=\"n\">Server</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"n\">std</span><span class=\"o\">::</span><span class=\"n\">string</span><span class=\"o\">&</span> <span class=\"n\">root</span><span class=\"p\">,</span>\n <span class=\"n\">ApplicationKiller</span><span class=\"o\">&</span> <span class=\"n\">applicationKiller</span><span class=\"p\">,</span>\n <span class=\"k\">const</span> <span class=\"n\">Util</span><span class=\"o\">::</span><span class=\"n\">AbstractConfiguration</span><span class=\"o\">&</span> <span class=\"n\">configurations</span><span class=\"p\">)</span> \n <span class=\"o\">:</span> <span class=\"n\">_blacklist</span><span class=\"p\">(</span><span class=\"n\">root</span> <span class=\"o\">+</span> <span class=\"s\">\"blacklist\"</span><span class=\"p\">,</span> <span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">),</span>\n <span class=\"n\">_applicationKiller</span><span class=\"p\">(</span><span class=\"n\">applicationKiller</span><span class=\"p\">),</span>\n <span class=\"n\">_hasOnRealTime</span><span class=\"p\">(</span><span class=\"nb\">true</span><span class=\"p\">),</span>\n <span class=\"n\">_pService</span><span class=\"p\">(</span><span class=\"nb\">NULL</span><span class=\"p\">),</span>\n <span class=\"n\">luaMail</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"o\">=</span><span class=\"n\">Script</span><span class=\"o\">::</span><span class=\"n\">CreateState</span><span class=\"p\">(),</span>\n <span class=\"n\">configurations</span><span class=\"p\">.</span><span class=\"n\">getString</span><span class=\"p\">(</span><span class=\"s\">\"smtp.host\"</span><span class=\"p\">,</span><span class=\"s\">\"localhost\"</span><span class=\"p\">),</span>\n <span class=\"n\">configurations</span><span class=\"p\">.</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"smtp.port\"</span><span class=\"p\">,</span><span class=\"n\">SMTPSession</span><span class=\"o\">::</span><span class=\"n\">SMTP_PORT</span><span class=\"p\">),</span>\n <span class=\"n\">configurations</span><span class=\"p\">.</span><span class=\"n\">getInt</span><span class=\"p\">(</span><span class=\"s\">\"smtp.timeout\"</span><span class=\"p\">,</span><span class=\"mi\">60</span><span class=\"p\">))</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>下面调用 <code class=\"language-plaintext highlighter-rouge\">Poco::File</code> 创建目录:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">File</span><span class=\"p\">((</span><span class=\"n\">string</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">WWWPath</span> <span class=\"o\">=</span> <span class=\"n\">root</span> <span class=\"o\">+</span> <span class=\"s\">\"www\"</span><span class=\"p\">).</span><span class=\"n\">createDirectory</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>因为 <code class=\"language-plaintext highlighter-rouge\">roor</code> 是 <code class=\"language-plaintext highlighter-rouge\">/Users/michael/Development/workspace/eclipse/OpenRTMFP-Cumulus/Debug/</code> 目录,所以 <code class=\"language-plaintext highlighter-rouge\">WWWPath</code> 就是 <code class=\"language-plaintext highlighter-rouge\">/Users/michael/Development/workspace/eclipse/OpenRTMFP-Cumulus/Debug/www</code> 目录。然后初始化 <code class=\"language-plaintext highlighter-rouge\">GlobalTable</code>,这个 <code class=\"language-plaintext highlighter-rouge\">GlobalTable</code> 是和 Lua 有关的东东,这里暂不细说,先知道与 Lua 相关就好。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Service</span><span class=\"o\">::</span><span class=\"n\">InitGlobalTable</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>下面就涉及到了 Lua script 了:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">SCRIPT_BEGIN</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">)</span>\n <span class=\"n\">SCRIPT_CREATE_PERSISTENT_OBJECT</span><span class=\"p\">(</span><span class=\"n\">Invoker</span><span class=\"p\">,</span><span class=\"n\">LUAInvoker</span><span class=\"p\">,</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">)</span>\n <span class=\"n\">readNextConfig</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">,</span><span class=\"n\">configurations</span><span class=\"p\">,</span><span class=\"s\">\"\"</span><span class=\"p\">);</span>\n <span class=\"n\">lua_setglobal</span><span class=\"p\">(</span><span class=\"n\">_pState</span><span class=\"p\">,</span><span class=\"s\">\"cumulus.configs\"</span><span class=\"p\">);</span>\n <span class=\"n\">SCRIPT_END</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>其中 <code class=\"language-plaintext highlighter-rouge\">SCRIPT_BEGIN</code>、<code class=\"language-plaintext highlighter-rouge\">SCRIPT_CREATE_PERSISTENT_OBJECT和SCRIPT_END</code> 都是宏,其定义在 <code class=\"language-plaintext highlighter-rouge\">Script.h</code> 文件中,如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>#define SCRIPT_BEGIN(STATE) \\\n if (lua_State* __pState = STATE) { \\\n const char* __error=NULL;\n \n#define SCRIPT_CREATE_PERSISTENT_OBJECT(TYPE,LUATYPE,OBJ) \\\n Script::WritePersistentObject<TYPE,LUATYPE>(__pState,OBJ); \\\n lua_pop(__pState,1);\n \n#define SCRIPT_END }\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">SCRIPT_BEGIN和SCRIPT_END</code> 经常用到,当与 Lua 相关的操作出现时,都会以这两个宏作为开头和结尾。</p>\n\n<h4 id=\"2maincpp-中-main-函数的-serverstart\">2、<code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 中 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数的 <code class=\"language-plaintext highlighter-rouge\">server.start()</code></h4>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"n\">RTMFPServerParams</span><span class=\"o\">&</span> <span class=\"n\">params</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n</code></pre></div></div>\n\n<p>如果 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusServer</code> 正在运行,则返回并终止启动。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">running</span><span class=\"p\">())</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer server is yet running, call stop method before\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设定端口号,如果端口号为 0,则返回并终止启动。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_port</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">port</span><span class=\"p\">;</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_port</span> <span class=\"o\">==</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer port must have a positive value\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>设定 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusEdge</code> 的端口号,如果其端口号与 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP/CumulusSever</code> 端口号相同,则返回并终止启动:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_edgesPort</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesPort</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_port</span> <span class=\"o\">==</span> <span class=\"n\">_edgesPort</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">ERROR</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer port must different than RTMFPServer edges.port\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>Cirrus:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_freqManage</span> <span class=\"o\">=</span> <span class=\"mi\">2000000</span><span class=\"p\">;</span> <span class=\"c1\">// 2 sec by default</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">pCirrus</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">_pCirrus</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"n\">Target</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">pCirrus</span><span class=\"p\">);</span>\n <span class=\"n\">_freqManage</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span> <span class=\"c1\">// no waiting, direct process in the middle case!</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer started in man-in-the-middle mode with server %s \\\n (unstable debug mode)\"</span><span class=\"p\">,</span> <span class=\"n\">_pCirrus</span><span class=\"o\">-></span><span class=\"n\">address</span><span class=\"p\">.</span><span class=\"n\">toString</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>middle:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_middle</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">middle</span><span class=\"p\">;</span>\n <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_middle</span><span class=\"p\">)</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer started in man-in-the-middle mode between peers \\\n (unstable debug mode)\"</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>UDP Buffer:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> \n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span><span class=\"o\">==</span><span class=\"mi\">0</span> <span class=\"o\">?</span> \n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">getReceiveBufferSize</span><span class=\"p\">()</span> <span class=\"o\">:</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">udpBufferSize</span><span class=\"p\">;</span>\n \n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">setReceiveBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_socket</span><span class=\"p\">.</span><span class=\"n\">setSendBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">setReceiveBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n <span class=\"n\">_edgesSocket</span><span class=\"p\">.</span><span class=\"n\">setSendBufferSize</span><span class=\"p\">(</span><span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n \n <span class=\"n\">DEBUG</span><span class=\"p\">(</span><span class=\"s\">\"Socket buffer receving/sending size = %u/%u\"</span><span class=\"p\">,</span>\n <span class=\"n\">udpBufferSize</span><span class=\"p\">,</span>\n <span class=\"n\">udpBufferSize</span><span class=\"p\">);</span>\n \n <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> \n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span> <span class=\"o\"><</span> <span class=\"mi\">5</span> <span class=\"o\">?</span> <span class=\"mi\">5000</span> <span class=\"o\">:</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAliveServer</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"n\">UInt32</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> \n <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\"><</span> <span class=\"mi\">5</span> <span class=\"o\">?</span> <span class=\"mi\">5000</span> <span class=\"o\">:</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">keepAlivePeer</span> <span class=\"o\">*</span> <span class=\"mi\">1000</span><span class=\"p\">;</span>\n <span class=\"p\">(</span><span class=\"n\">UInt8</span><span class=\"o\">&</span><span class=\"p\">)</span><span class=\"n\">edgesAttemptsBeforeFallback</span> <span class=\"o\">=</span> <span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">edgesAttemptsBeforeFallback</span><span class=\"p\">;</span>\n \n <span class=\"n\">setPriority</span><span class=\"p\">(</span><span class=\"n\">params</span><span class=\"p\">.</span><span class=\"n\">threadPriority</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>启动线程,进入循环运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">();</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>上句具体的源码实现为:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">start</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">running</span><span class=\"p\">())</span>\n <span class=\"k\">return</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>如果在运行则返回并终止启动。然后加一个局部锁。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">ScopedLock</span><span class=\"o\"><</span><span class=\"n\">FastMutex</span><span class=\"o\">></span> <span class=\"n\">lock</span><span class=\"p\">(</span><span class=\"n\">_mutex</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果不得不join()到主线程中,那就join()吧</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span><span class=\"p\">(</span><span class=\"n\">_haveToJoin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">();</span>\n <span class=\"n\">_haveToJoin</span><span class=\"o\">=</span><span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>然后就运行这个线程吧:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">_terminate</span> <span class=\"o\">=</span> <span class=\"nb\">false</span><span class=\"p\">;</span>\n <span class=\"kr\">_thread</span><span class=\"p\">.</span><span class=\"n\">start</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"k\">this</span><span class=\"p\">);</span>\n <span class=\"n\">_haveToJoin</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3回顾一下整个启动流程\">3、回顾一下整个启动流程</h4>\n\n<p>到此我们先回顾一下启动过程:</p>\n\n<p>从 <code class=\"language-plaintext highlighter-rouge\">main.cpp</code> 的启动入口 <code class=\"language-plaintext highlighter-rouge\">main()</code> 函数开始,创建 <code class=\"language-plaintext highlighter-rouge\">Server</code> 对象并启动(调用 <code class=\"language-plaintext highlighter-rouge\">start()</code> 函数)。<code class=\"language-plaintext highlighter-rouge\">Server::start()</code> 中调用其父类(<code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code>)的父类(<code class=\"language-plaintext highlighter-rouge\">Startable</code>)的方法 <code class=\"language-plaintext highlighter-rouge\">Startable::start()</code> 开启线程。\n调用 <code class=\"language-plaintext highlighter-rouge\">Startable::start()</code> 函数后,开启线城时传入的参数为 <code class=\"language-plaintext highlighter-rouge\">*this</code>,所以就会运行 <code class=\"language-plaintext highlighter-rouge\">Startable::run()</code>;</p>\n\n<h4 id=\"4rtmfpserverprerunstartableprerun-和-rtmfpserverrun-函数源码\">4、<code class=\"language-plaintext highlighter-rouge\">RTMFPServer::prerun()</code>、<code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 和 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::run(...)</code> 函数源码</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Startable::run()</code> 调用 <code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 函数,但这个函数被 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 覆盖,所以会运行 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer::prerun()</code>,其源码如下:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">prerun</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFP server starts on %u port\"</span><span class=\"p\">,</span><span class=\"n\">_port</span><span class=\"p\">);</span>\n</code></pre></div></div>\n\n<p>如果CumulusEdge:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">_edgesPort</span><span class=\"o\">></span><span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFP edges server starts on %u port\"</span><span class=\"p\">,</span><span class=\"n\">_edgesPort</span><span class=\"p\">);</span>\n \n <span class=\"kt\">bool</span> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"nb\">true</span><span class=\"p\">;</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"n\">onStart</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>运行线程:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">prerun</span><span class=\"p\">();</span>\n</code></pre></div></div>\n\n<p>处理异常:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"err\">}</span> <span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"n\">Exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">displayText</span><span class=\"p\">().</span><span class=\"n\">c_str</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"n\">exception</span><span class=\"o\">&</span> <span class=\"n\">ex</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer : %s\"</span><span class=\"p\">,</span><span class=\"n\">ex</span><span class=\"p\">.</span><span class=\"n\">what</span><span class=\"p\">());</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(...)</span> <span class=\"p\">{</span>\n <span class=\"n\">FATAL</span><span class=\"p\">(</span><span class=\"s\">\"RTMFPServer unknown error\"</span><span class=\"p\">);</span>\n <span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>如果跳出了,则终止运行:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"n\">onStop</span><span class=\"p\">();</span>\n \n <span class=\"n\">NOTE</span><span class=\"p\">(</span><span class=\"s\">\"RTMFP server stops\"</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"n\">result</span><span class=\"p\">;</span>\n<span class=\"err\">}</span>\n</code></pre></div></div>\n\n<p>该函数内部又会调用父类的 <code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 函数,该函数调用:</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">virtual</span> <span class=\"kt\">void</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"k\">volatile</span> <span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span>\n</code></pre></div></div>\n\n<p>它是一个纯虚函数,由 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 实现。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Startable::prerun()</code> 会调用 <code class=\"language-plaintext highlighter-rouge\">void run(const volatile bool& terminate)</code> 方法,该方法被 <code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 覆盖了。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">bool</span> <span class=\"n\">Startable</span><span class=\"o\">::</span><span class=\"n\">prerun</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n <span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">_terminate</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"o\">!</span><span class=\"n\">_terminate</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RTMFPServer</code> 覆盖 <code class=\"language-plaintext highlighter-rouge\">Startable</code> 的 <code class=\"language-plaintext highlighter-rouge\">run(const volatile bool &terminate)</code> 方法。</p>\n\n<div class=\"language-c++ highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">void</span> <span class=\"n\">RTMFPServer</span><span class=\"o\">::</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"k\">const</span> <span class=\"k\">volatile</span> <span class=\"kt\">bool</span><span class=\"o\">&</span> <span class=\"n\">terminate</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"p\">...</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 1:入门介绍、部署与 Hello World</title>\n \t<meta name=\"description\" content=\"RTMFP 是 Adobe 开发的基于 UDP 协议的实时传输媒体流协议,支持 P2P 传输,具有较高的实时性和安全性。它的主要应用场景是视频通信、语音通信和网络游戏。OpenRTMFP 是一个开源的 RTMFP 实现,可以用于构建基于 RTMFP 的应用程序。Cumulus 是一个基于 OpenRTMFP 的服务器,提供 RTMFP 服务。它具有轻量级、跨平台和可扩展的特点,并且还提供了负载均衡和可扩展性解决方案。YY 语音的 Web 端音视频流媒体能力,正是基于 RTMFP 协议做的迭代优化实现的。本文是船长关于这个系列文章的第一篇。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 1:入门介绍、部署与 Hello World</h2>\t\t\n\t<time datetime=\"2012-04-09T18:57:19+00:00\" class=\"by-line\">09 Apr 2012, 广州 | 麦克船长 | 总计 8401 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一rtmfp是什么\" id=\"markdown-toc-一rtmfp是什么\">一、RTMFP 是什么?</a> <ul>\n <li><a href=\"#文件分享-p2p-和实时流媒体-p2p-的区别是什么\" id=\"markdown-toc-文件分享-p2p-和实时流媒体-p2p-的区别是什么\">文件分享 P2P 和实时流媒体 P2P 的区别是什么?</a></li>\n <li><a href=\"#rtmfp-和-rtmp-之间的区别是什么\" id=\"markdown-toc-rtmfp-和-rtmp-之间的区别是什么\">RTMFP 和 RTMP 之间的区别是什么?</a></li>\n <li><a href=\"#flash-player-支持-rtmfp-吗\" id=\"markdown-toc-flash-player-支持-rtmfp-吗\">Flash Player 支持 RTMFP 吗?</a></li>\n <li><a href=\"#cumulus-使用-adobe-的-cirrus-key-吗\" id=\"markdown-toc-cumulus-使用-adobe-的-cirrus-key-吗\">Cumulus 使用 Adobe 的 Cirrus Key 吗?</a></li>\n <li><a href=\"#这个开源项目合法吗\" id=\"markdown-toc-这个开源项目合法吗\">这个开源项目合法吗?</a></li>\n </ul>\n </li>\n <li><a href=\"#二openrtmfp和cumulus\" id=\"markdown-toc-二openrtmfp和cumulus\">二、OpenRTMFP 和 Cumulus</a></li>\n <li><a href=\"#三入门介绍与部署cumulusserver\" id=\"markdown-toc-三入门介绍与部署cumulusserver\">三、入门介绍与部署 CumulusServer</a> <ul>\n <li><a href=\"#1背景介绍\" id=\"markdown-toc-1背景介绍\">1、背景介绍</a></li>\n <li><a href=\"#2准备工作\" id=\"markdown-toc-2准备工作\">2、准备工作</a></li>\n <li><a href=\"#3安装\" id=\"markdown-toc-3安装\">3、安装</a> <ul>\n <li><a href=\"#31外部依赖的安装\" id=\"markdown-toc-31外部依赖的安装\">3.1、外部依赖的安装</a></li>\n <li><a href=\"#32安装openrtmfpcumulus\" id=\"markdown-toc-32安装openrtmfpcumulus\">3.2、安装 OpenRTMFP/Cumulus</a></li>\n </ul>\n </li>\n <li><a href=\"#4配置\" id=\"markdown-toc-4配置\">4、配置</a></li>\n <li><a href=\"#5启动\" id=\"markdown-toc-5启动\">5、启动</a></li>\n <li><a href=\"#6基本使用\" id=\"markdown-toc-6基本使用\">6、基本使用</a></li>\n <li><a href=\"#7扩展cumulusserverserverapplication\" id=\"markdown-toc-7扩展cumulusserverserverapplication\">7、扩展 CumulusServer(Server Application)</a></li>\n </ul>\n </li>\n <li><a href=\"#三用lua编写helloworld应用扩展cumulusserver\" id=\"markdown-toc-三用lua编写helloworld应用扩展cumulusserver\">三、用 Lua 编写 HelloWorld 应用扩展 CumulusServer</a> <ul>\n <li><a href=\"#1server-side\" id=\"markdown-toc-1server-side\">1、Server-side</a> <ul>\n <li><a href=\"#11serverconfiguration\" id=\"markdown-toc-11serverconfiguration\">1.1、Server configuration</a></li>\n <li><a href=\"#12applicationfile\" id=\"markdown-toc-12applicationfile\">1.2、Application file</a></li>\n </ul>\n </li>\n <li><a href=\"#2client-side\" id=\"markdown-toc-2client-side\">2、Client-side</a></li>\n <li><a href=\"#3运行结果\" id=\"markdown-toc-3运行结果\">3、运行结果</a></li>\n <li><a href=\"#4远程测试一个免费的测试服务器\" id=\"markdown-toc-4远程测试一个免费的测试服务器\">4、远程测试:一个免费的测试服务器</a></li>\n </ul>\n </li>\n</ul>\n\n<h3 id=\"一rtmfp是什么\">一、RTMFP 是什么?</h3>\n\n<p>Real-Time Media Flow Protocol(RTMFP)是 Adobe 开发的一种基于 UDP 并支持 P2P 的实时传输媒体流。主要特点是:高传输效率(可以使用压缩和算法来优化流量从而提高传输效率)、高实时性(可以保证媒体流的实时性使得视频通信和其他实时通信更加流畅)、支持 P2P 传输(减少对服务器的依赖从而减少带宽和服务器资源消耗)、高安全性(加密媒体流从而保证其安全性)。</p>\n\n<p>RTMFP 的主要应用场景包括:视频通信(视频聊天和视频会议)、语音通信(语音聊天、电话)、网络游戏。不过 RTMFP 目前仅有 Adobe 开发的版本,所以它并不是个开源项目,而是个商业化服务。那么有没有开源版本呢?</p>\n\n<h4 id=\"文件分享-p2p-和实时流媒体-p2p-的区别是什么\">文件分享 P2P 和实时流媒体 P2P 的区别是什么?</h4>\n\n<p>RTMFP 是一个 P2P 系统,但它仅针对实时通信时直接用户到用户之间的通信而设计,不能用于多个对等方之间进行文件共享(使用分段下载)。Facebook 在其 Pipe 应用中使用此协议将大文件直接在两个用户之间传输。</p>\n\n<h4 id=\"rtmfp-和-rtmp-之间的区别是什么\">RTMFP 和 RTMP 之间的区别是什么?</h4>\n\n<p>RTMP 是实时消息协议,RTMFP 代表实时媒体流协议。RTMFP 基于用户数据报协议(UDP),而 RTMP 基于传输控制协议(TCP)。\n与 RTMP 不同,RTMFP 还支持直接从一个 Adobe Flash Player 传输数据到另一个,而无需经过服务器。</p>\n\n<h4 id=\"flash-player-支持-rtmfp-吗\">Flash Player 支持 RTMFP 吗?</h4>\n\n<p>RTMFP 是基于用户数据报协议(UDP)的,而 RTMP 是基于传输控制协议(TCP)的。与 RTMP 不同,RTMFP 还支持直接在两个 Adobe Flash Player 之间发送数据,而不经过服务器。Flash Player 10.0 仅允许一对一通信进行 P2P,但从 10.1 开始允许应用程序级别的多播。Flash Player 查找适当的分发路由(覆盖网络),并可以将其分发到通过 P2P 连接的组。</p>\n\n<h4 id=\"cumulus-使用-adobe-的-cirrus-key-吗\">Cumulus 使用 Adobe 的 Cirrus Key 吗?</h4>\n\n<p>不!当然,这是Cumulus的主要目标:成为Cirrus GPL的替代品。唯一的限制是:你的CPU,内存和单台机器的端口数。</p>\n\n<h4 id=\"这个开源项目合法吗\">这个开源项目合法吗?</h4>\n\n<p>在美国,数字千年版权法(Digital Millennium Copyright Act)规定,逆向工程用于协议互操作性是合法的。你可以在 WikiPedia 上查看相关讨论:</p>\n\n<ul>\n <li>http://en.wikipedia.org/wiki/Real_Time_Media_Flow_Protocol</li>\n <li>http://en.wikipedia.org/wiki/Proprietary_protocol</li>\n <li>http://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act</li>\n</ul>\n\n<p>当逆向工程的目的是协议互操作性时,有法律先例。在美国,数字千年版权法(Digital Millennium Copyright Act)为逆向工程软件以使其与其他软件互操作提供了安全保障。</p>\n\n<h3 id=\"二openrtmfp和cumulus\">二、OpenRTMFP 和 Cumulus</h3>\n\n<p>OpenRTMFP 是一个开源的 RTMFP 实现,可以用于构建基于 RTMFP 的应用程序。它包含了 RTMFP 协议的实现,以及一些额外的功能,如媒体流传输、P2P 通信、脚本引擎和数据存储。</p>\n\n<p>Cumulus 是一个基于 OpenRTMFP 的服务器,是一个完整的开源且跨平台的 RTMFP 服务器,可通过脚本进行扩展。CumulusServer 根据 GPL 许可在考虑以下 4 个概念的情况下开发:速度、轻量、跨平台和可扩展。尽管尚未发布版本,但只有在 CumulusServer 经过测试和批准后才会将代码推送到 github。实际上,主要稳定的功能有:</p>\n\n<ul>\n <li>P2P rendez-vous service</li>\n <li>live streaming</li>\n <li>RPC、pull、push exchange,实际上客户端和服务器之间的所有 AMF 可能交换</li>\n <li>脚本引擎,用于创建自己的应用服务器或扩展 Cumulus 的功能</li>\n <li>可扩展性和负载均衡解决方案</li>\n</ul>\n\n<p>下面的内容是本篇 blog 的重点,包括两部分:先是 OpenRTMFP 应用的核心 CumulusServer 的入门介绍与部署,然后用 Lua 编写 HelloWorld 应用扩展 CumulusServer,我们开始吧!</p>\n\n<h3 id=\"三入门介绍与部署cumulusserver\">三、入门介绍与部署 CumulusServer</h3>\n\n<h4 id=\"1背景介绍\">1、背景介绍</h4>\n\n<p>OpenRTMFP 可以帮助你实现 Flash 的实时应用的高并发扩展,OpenRTMFP/Cumulus 是基于 GNU General Public License 的。</p>\n\n<p>POCO:POrtable COmponents,是一个强大的开源 C++ 库。其在 C++ 开发中的角色,相当于 Java Class Library、苹果的 Cocoa、.NET framework。</p>\n\n<h4 id=\"2准备工作\">2、准备工作</h4>\n\n<p>下载:</p>\n\n<table>\n <thead>\n <tr>\n <th><strong>External Dependencies</strong></th>\n <th><strong>Official Site</strong></th>\n <th><strong>Windows</strong></th>\n <th><strong>Linux/OSX</strong></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>OpenSSL</td>\n <td><a href=\"http://www.slproweb.com/products/Win32OpenSSL.html\">Official Site</a></td>\n <td><a href=\"http://www.slproweb.com/download/Win32OpenSSL_Light-1_0_1.exe\">Download</a></td>\n <td><a href=\"http://www.openssl.org/source/openssl-1.0.1.tar.gz\">Download</a></td>\n </tr>\n <tr>\n <td>Lua</td>\n <td><a href=\"http://www.lua.org/\">Official Site</a></td>\n <td><a href=\"http://luaforwindows.googlecode.com/files/LuaForWindows_v5.1.4-45.exe\">Download</a></td>\n <td><a href=\"http://www.lua.org/ftp/lua-5.1.5.tar.gz\">Download</a></td>\n </tr>\n <tr>\n <td>POCO</td>\n <td><a href=\"http://pocoproject.org/\">Official Site</a></td>\n <td><a href=\"http://downloads.sourceforge.net/project/poco/sources/poco-1.4.3/poco-1.4.3p1.zip\">Download</a></td>\n <td><a href=\"https://sourceforge.net/projects/poco/files/sources/poco-1.4.3/poco-1.4.3p1.tar.gz/download\">Download</a></td>\n </tr>\n </tbody>\n</table>\n\n<p>注意:POCO for linux 版本必须是 1.4.0 或更高,否则会引起 TCP 相关的 bug。</p>\n\n<h4 id=\"3安装\">3、安装</h4>\n\n<h5 id=\"31外部依赖的安装\">3.1、外部依赖的安装</h5>\n\n<p>Windows 下略,Linux 下基本就是:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span>./configuremakesudo\n<span class=\"nv\">$ </span>make <span class=\"nb\">install</span>\n</code></pre></div></div>\n\n<h5 id=\"32安装openrtmfpcumulus\">3.2、安装 OpenRTMFP/Cumulus</h5>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">cd </span>OpenRTMFP-Cumulus/CumulusLib\n<span class=\"nv\">$ </span>make\n<span class=\"nv\">$ </span><span class=\"nb\">cd</span> ../CumulusServer\n<span class=\"nv\">$ </span>make\n</code></pre></div></div>\n\n<p>如果出现了 <code class=\"language-plaintext highlighter-rouge\">.h</code> 文件、lib 库找不到的情况,请修改 OpenRTMFP-Cumulus/CumulusLib/Makefile 或 OpenRTMFP-Cumulus/CumulusServer/Makefile。</p>\n\n<h4 id=\"4配置\">4、配置</h4>\n\n<p>通过编写 <code class=\"language-plaintext highlighter-rouge\">OpenRTMFP-Cumulus/CumulusServer/CumulusServer.ini</code> 文件来为 OpenRTMFP-Cumulus 进行个性化配置(默认是没有这个文件的),这个文件的内容形如:</p>\n\n<div class=\"language-lua highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">;</span><span class=\"n\">CumulusServer</span><span class=\"p\">.</span><span class=\"n\">ini</span>\n<span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"mi\">1985</span>\n<span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> <span class=\"mi\">114688</span>\n<span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> <span class=\"mi\">15</span>\n<span class=\"p\">[</span><span class=\"n\">logs</span><span class=\"p\">]</span>\n<span class=\"n\">name</span><span class=\"o\">=</span><span class=\"n\">log</span>\n<span class=\"n\">directory</span><span class=\"o\">=</span><span class=\"n\">C</span><span class=\"p\">:</span><span class=\"o\">/</span><span class=\"n\">CumulusServer</span><span class=\"o\">/</span><span class=\"n\">logs</span>\n</code></pre></div></div>\n\n<p>一些字段的设置含义如下,摘自:<a href=\"https://github.com/OpenRTMFP/Cumulus/wiki/Installation\">地址</a>。</p>\n\n<ul>\n <li>公开给 Client 的端口号 <code class=\"language-plaintext highlighter-rouge\">port</code>,默认值是 1935(RTMFP 服务器的默认端口),用于 CumulusServer 监听 RTMFP 请求。</li>\n <li>UDP 缓冲区字节数 <code class=\"language-plaintext highlighter-rouge\">udpBufferSize</code>, allows to change the size in bytes of UDP reception and sending buffer. Increases this value if your operating system has a default value too lower for important loads.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">keepAliveServer</code>, time in seconds for periodically sending packets keep-alive with server, 15s by default (valid value is from 5s to 255s).</li>\n <li><code class=\"language-plaintext highlighter-rouge\">keepAlivePeer</code>, time in seconds for periodically sending packets keep-alive between peers, 10s by default (valid value is from 5s to 255s).</li>\n <li><code class=\"language-plaintext highlighter-rouge\">edges.activated</code>, activate or not the edges server on the RTMFP server (see CumulusEdge, Scalability page for more details about CumulusEdge). By default, CumulusServer stays a RTMFP server without edges ability (default value is false).</li>\n <li><code class=\"language-plaintext highlighter-rouge\">edges.port</code>, port for the edges server, to accept incoming new CumulusEdge instances (see CumulusEdge, Scalability page for more details about CumulusEdge). By default, it’s the port 1936.</li>\n</ul>\n\n<blockquote>\n <p>Warning: This port will receive plain text request from edges, for this purpose it should not be made public. It’s very important for security consideration. It must be available only for CumulusEdge instances, and anything else.</p>\n</blockquote>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">edges.attemptsBeforeFallback</code>, number of CumulusEdge attempt connections before falling back to CumulusServer (see CumulusEdge, Scalability page for more details about CumulusEdge). By default the value is 2 (in practical, 2 attempts happens after 5 sec approximately).</li>\n <li>SMTP IP 地址 <code class=\"language-plaintext highlighter-rouge\">smtp.host</code>, configure a SMTP host to use mails feature provided by Cumulus in server application (see Server Application, Sockets page for more details about mails feature). By default the value is localhost.</li>\n <li>SMTP 端口 <code class=\"language-plaintext highlighter-rouge\">smtp.port</code>, configure a SMTP port to use mails feature provided by Cumulus in server application (see Server Application, Sockets page for more details about mails feature). By default the value is 25.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">smtp.timeout</code>, configure a SMTP timeout session in seconds to use mails feature provided by Cumulus in server application (see Server Application, Sockets page for more details about mails feature). By default the value is 60 seconds.</li>\n <li>日志路径 <code class=\"language-plaintext highlighter-rouge\">logs.directory</code>,默认是 <code class=\"language-plaintext highlighter-rouge\">CumulusServer/logsby</code>。</li>\n <li>日志文件名称 <code class=\"language-plaintext highlighter-rouge\">logs.name</code>,默认是<code class=\"language-plaintext highlighter-rouge\">log</code>。</li>\n</ul>\n\n<h4 id=\"5启动\">5、启动</h4>\n\n<p>Windows 下的启动方法为:</p>\n\n<pre><code class=\"language-dos\">$ CumulusServer.exe /registerService [/displayName=CumulusServer /description=\"Open Source RTMFP Server\" /startup=automatic]\n</code></pre>\n\n<p>Unix-like 下的启动方法为:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">sudo</span> ./CumulusServer <span class=\"nt\">--daemon</span> <span class=\"o\">[</span><span class=\"nt\">--pidfile</span><span class=\"o\">=</span>/var/run/CumulusServer.pid]\n</code></pre></div></div>\n\n<p>具体地,我的启动命令为:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">sudo</span> ./CumulusServer <span class=\"nt\">--daemon</span> <span class=\"nt\">--pidfile</span><span class=\"o\">=</span>./CumulusServer.pid\n</code></pre></div></div>\n\n<h4 id=\"6基本使用\">6、基本使用</h4>\n\n<p>本地 Flash client 可以通过如下语句连接:</p>\n\n<div class=\"language-as highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">$</span> <span class=\"kd\">var</span> <span class=\"nx\">nc</span><span class=\"o\">:</span><span class=\"nx\">NetConnection</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nx\">NetConnection</span><span class=\"p\">()</span><span class=\"o\">;</span><span class=\"nx\">nc</span><span class=\"p\">.</span><span class=\"nx\">connect</span><span class=\"p\">(</span><span class=\"s2\">\"rtmfp://localhost/\"</span><span class=\"p\">)</span><span class=\"o\">;</span>\n</code></pre></div></div>\n\n<p>RTMFP默认是采用1935端口,如果你特别指定了其他端口,比如12345,请使用如下方式:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>nc.connect(\"rtmfp://localhost:12345/\");\n</code></pre></div></div>\n\n<h4 id=\"7扩展cumulusserverserverapplication\">7、扩展 CumulusServer(Server Application)</h4>\n\n<p>启动CumulusServer后,会在可执行文件的目录下出现一个www目录,该目录的作用,就是作为 Server Application 的默认根目录。具体的对应关系如下:</p>\n\n<blockquote>\n <p>rtmfp://host:port/ -> [CumulusServer folder]/www/main.lua (root application)</p>\n</blockquote>\n\n<blockquote>\n <p>rtmfp://host:port/myApplication -> [CumulusServer folder]/www/myApplication/main.lua</p>\n</blockquote>\n\n<blockquote>\n <p>rtmfp://host:port/Games/myGame -> [CumulusServer folder]/www/Games/myGame/main.lua</p>\n</blockquote>\n\n<p>另外要提醒的是,如果main.lua文件被修改,则不需要重启 CumulusServer,因为 Server Application 的创建是一种动态的方式。</p>\n\n<h3 id=\"三用lua编写helloworld应用扩展cumulusserver\">三、用 Lua 编写 HelloWorld 应用扩展 CumulusServer</h3>\n\n<p>下面的这个实例是在本地(Client 与 Server 位于同一机器上)测试的。</p>\n\n<h4 id=\"1server-side\">1、Server-side</h4>\n\n<h5 id=\"11serverconfiguration\">1.1、Server configuration</h5>\n\n<div class=\"language-lua highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">;</span> <span class=\"n\">CumulusServer</span><span class=\"p\">.</span><span class=\"n\">ini</span>\n<span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"mi\">1935</span>\n<span class=\"n\">udpBufferSize</span> <span class=\"o\">=</span> <span class=\"mi\">114688</span>\n<span class=\"n\">keepAlivePeer</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">keepAliveServer</span> <span class=\"o\">=</span> <span class=\"mi\">15</span>\n<span class=\"p\">[</span><span class=\"n\">logs</span><span class=\"p\">]</span><span class=\"n\">name</span> <span class=\"o\">=</span> <span class=\"n\">log</span>\n<span class=\"n\">directory</span> <span class=\"o\">=</span> <span class=\"n\">logs</span>\n</code></pre></div></div>\n\n<h5 id=\"12applicationfile\">1.2、Application file</h5>\n\n<div class=\"language-lua highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code> <span class=\"k\">function</span> <span class=\"nf\">onConnection</span><span class=\"p\">(</span><span class=\"n\">client</span><span class=\"p\">,</span><span class=\"n\">response</span><span class=\"p\">,</span><span class=\"o\">...</span><span class=\"p\">)</span>\n <span class=\"k\">function</span> <span class=\"nf\">client</span><span class=\"p\">:</span><span class=\"n\">test</span><span class=\"p\">(</span><span class=\"o\">...</span><span class=\"p\">)</span>\n <span class=\"n\">name</span><span class=\"p\">,</span><span class=\"n\">firstname</span> <span class=\"o\">=</span> <span class=\"n\">unpack</span><span class=\"p\">(</span><span class=\"n\">arg</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"s2\">\"Hello \"</span><span class=\"o\">..</span><span class=\"n\">firstname</span><span class=\"o\">..</span><span class=\"s2\">\" \"</span><span class=\"o\">..</span><span class=\"n\">name</span>\n <span class=\"k\">end</span>\n <span class=\"k\">end</span>\n</code></pre></div></div>\n\n<h4 id=\"2client-side\">2、Client-side</h4>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">// CumulusClient.as</span>\n\n<span class=\"kn\">package</span> <span class=\"err\">{</span>\n <span class=\"nn\">import</span> <span class=\"n\">flash</span><span class=\"o\">.</span><span class=\"na\">display</span><span class=\"o\">.</span><span class=\"na\">Sprite</span><span class=\"o\">;</span>\n <span class=\"kn\">import</span> <span class=\"nn\">flash.net.NetConnection</span><span class=\"o\">;</span>\n <span class=\"kn\">import</span> <span class=\"nn\">flash.net.NetStream</span><span class=\"o\">;</span>\n <span class=\"kn\">import</span> <span class=\"nn\">flash.net.Responder</span><span class=\"o\">;</span>\n\n <span class=\"kd\">public</span> <span class=\"kd\">class</span> <span class=\"nc\">CumulusClient</span> <span class=\"kd\">extends</span> <span class=\"nc\">Sprite</span> <span class=\"o\">{</span>\n <span class=\"kd\">private</span> <span class=\"kt\">var</span> <span class=\"nl\">nc:</span><span class=\"nc\">NetConnection</span> <span class=\"o\">=</span> <span class=\"kc\">null</span><span class=\"o\">;</span>\n \t<span class=\"kd\">private</span> <span class=\"kt\">var</span> <span class=\"nl\">ns:</span><span class=\"nc\">NetStream</span> <span class=\"o\">=</span> <span class=\"kc\">null</span><span class=\"o\">;</span>\n \t\n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">CumulusClient</span><span class=\"o\">()</span> <span class=\"o\">{</span>\n <span class=\"n\">nc</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">NetConnection</span><span class=\"o\">();</span>\n <span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">connect</span><span class=\"o\">(</span><span class=\"s\">\"rtmfp://localhost\"</span><span class=\"o\">);</span>\n <span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">client</span> <span class=\"o\">=</span> <span class=\"k\">this</span><span class=\"o\">;</span>\n <span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">call</span><span class=\"o\">(</span><span class=\"s\">\"test\"</span><span class=\"o\">,</span><span class=\"k\">new</span> <span class=\"nc\">Responder</span><span class=\"o\">(</span><span class=\"n\">onResult</span><span class=\"o\">,</span><span class=\"n\">onStatus</span><span class=\"o\">),</span> <span class=\"s\">\"OpenRTMFP/Cumulus\"</span><span class=\"o\">,</span> <span class=\"s\">\"World\"</span><span class=\"o\">)</span>\n <span class=\"o\">}</span>\n \n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">close</span><span class=\"o\">():</span><span class=\"kt\">void</span> <span class=\"o\">{</span> \n\t\t\t<span class=\"n\">nc</span><span class=\"o\">.</span><span class=\"na\">close</span><span class=\"o\">();</span>\n \t<span class=\"o\">}</span>\n \n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">onStatus</span><span class=\"o\">(</span><span class=\"nl\">status:</span><span class=\"nc\">Object</span><span class=\"o\">):</span><span class=\"kt\">void</span> <span class=\"o\">{</span>\n \t<span class=\"n\">trace</span><span class=\"o\">(</span><span class=\"n\">status</span><span class=\"o\">.</span><span class=\"na\">description</span><span class=\"o\">)</span>\n\t <span class=\"o\">}</span>\n \n \t<span class=\"kd\">public</span> <span class=\"n\">function</span> <span class=\"nf\">onResult</span><span class=\"o\">(</span><span class=\"nl\">response:</span><span class=\"nc\">Object</span><span class=\"o\">):</span><span class=\"kt\">void</span> <span class=\"o\">{</span>\n \t<span class=\"n\">trace</span><span class=\"o\">(</span><span class=\"n\">response</span><span class=\"o\">)</span> <span class=\"c1\">// expected to display \"Hello World OpenRTMFP/Cumulus\" </span>\n\t <span class=\"o\">}</span> \n\t<span class=\"o\">}</span>\n<span class=\"o\">}</span>\n</code></pre></div></div>\n\n<h4 id=\"3运行结果\">3、运行结果</h4>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Hello World OpenRTMFP/Cumulus\n[SWF] CumulusClient.swf - 解压缩后为 1,776 个字节\n[卸装 SWF] CumulusClient.swf\n</code></pre></div></div>\n\n<h4 id=\"4远程测试一个免费的测试服务器\">4、远程测试:一个免费的测试服务器</h4>\n\n<p>获取 Developer Key 的地址:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>http://108.59.252.39:8080/CumulusServer/index.jsp\n</code></pre></div></div>\n\n<p>服务器配置信息:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Server: amd64 OS: Linux 2.6.18-028stab095.1\nServer IP: 108.59.252.39\nOpenRTMFP as of: 22.Feb.2012\n</code></pre></div></div>\n\n<p>编写服务器段应用地址:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>http://108.59.252.39:8080/CumulusServer/manage_ssls.jsp\n</code></pre></div></div>\n\n<p>快去试试吧 :)</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"thinking":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>不要船开远了,就忘了为什么启航</title>\n \t<meta name=\"description\" content=\"2020 年的 6 月 4 日我入职阿里巴巴集团,7 天后的 6 月 11 日我写下了这篇文章。偶然翻到了当时这篇文章,遂转录于此,提醒自己勿忘初心。在不涉及到公司数据安全及商业机密问题的前提下,稍做了一些删改,发布在这里作为一个回顾。本次穿插了一些图片,当时写的时候还没有这些照片。本文内容包括:很多人是带着梦想来阿里的,那么我的梦想是什么呢?最喜欢新六脉的哪句话?为什么?关于阿里企业价值观:为什么要接受这套价值观?价值观的本质意义(极度务实视角)是什么?Landing 的 SOP;问问自己,来到阿里,如果初期我可能需要做一点改变,那会是什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>不要船开远了,就忘了为什么启航</h2>\t\t\n\t<time datetime=\"2022-08-11T15:53:57+00:00\" class=\"by-line\">11 Aug 2022, 杭州 | 麦克船长 | 总计 3223 字</time>\n\t<div class=\"content\">\n\t\t<h3 id=\"写在前面\">写在前面</h3>\n<p>偶然翻到 2020.06.11 刚来到阿里时写的一篇内容(我是 2020 年的 6 月 4 日我入职阿里巴巴集团),是有关于来阿里的期待、对这家公司的一些粗浅初步的理解。此时再翻来看看,最大的感触就是,提醒自己勿忘初心。</p>\n\n<p>在不涉及到公司数据安全及商业机密问题的前提下,稍做了一些删改,发布在这里作为一个回顾。本次穿插了一些图片,当时写的时候还没有这些照片。本文内容包括:</p>\n\n<ul>\n <li>很多人是带着梦想来阿里的,那么我的梦想是什么呢?</li>\n <li>最喜欢新六脉的哪句话?为什么?</li>\n <li>关于阿里企业价值观:为什么要接受这套价值观?</li>\n <li>价值观的本质意义(极度务实视角)是什么?</li>\n <li>Landing 的 SOP</li>\n <li>问问自己,来到阿里,如果初期我可能需要做一点改变,那会是什么?</li>\n</ul>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-1.png\" alt=\"image\" /></p>\n\n<p>注:2020 年平安夜 · 百年湖畔 87 期合影</p>\n\n<h3 id=\"很多人是带着梦想来阿里的那么我的梦想是什么呢\">很多人是带着梦想来阿里的,那么我的梦想是什么呢?</h3>\n\n<p>Christensen 在《创新者的窘境》中提到:每一次技术更迭,都需要破坏性创新,而破坏性创新在前一次技术更迭的胜出者内部是很难生长出来的。阿里诞生以来,不断地创造第二增长曲线:阿里巴巴、淘宝、支付宝、天猫、阿里云、钉钉 …… 这让我非常好奇。其中很多产品穿越多个时间周期,期间不断创造内生二次曲线。</p>\n\n<p>但是阿里也一样错失了很多,微信、美团、拼多多、抖/快…… 等等很多产品诞生在了其他公司,还有某些产品在不断的科技更迭中自身生长出了第二曲线。</p>\n\n<p>因此我来阿里的梦想也非常明确:<strong>参与或创造一次(甚至多次)第二曲线,可以是新产品,也可以是原有产品内生的。在这个过程中获得个人成长、个人价值。</strong></p>\n\n<p>一直以来,我有三个最想实现或得到的东西:LOVE、CREATION、FREEDOM。随着生活与工作的前行,对这三者的理解,在不断加深。在这个问题里,我想应该是讨论”CREATION”。</p>\n\n<p>CREATION 上,我的梦想的范式,大概是从自己中学时代就确立了,在某一次人类社会变革浪潮中,扮演有一定权重的角色。这里面有几个变量:<strong>什么领域(F)的变革;什么规模(S)的变革;多大的权重(W);什么角色(R)。</strong></p>\n\n<p>F 这个变量,我在中学及大学时代逐渐明确,是以相对普适的产品形式输出结果并对社会变革产生积极作用。后来越来越明确为科技与商业结合的领域。</p>\n\n<p>S、W 这两个变量,自然是越大越好。因此我会希望能够构建尽可能大的机会,或者参与到尽可能大的机会中。R 希望是有强烈 Ownership 的身份。</p>\n\n<p>因此过去几年我选择了创业。创业就像冲浪,你抓住一次浪并完成漂亮的动作,就是一次不算失败的创业。但是如果一个浪没抓住,你去追它是没意义的,而应该等待下一个浪。我认为在未来 5~10 年内难以出现规模能大到令我足够兴奋的科技浪潮。大浪潮中属于创业者的大机会很多,而中小浪潮的大机会基本只属于大平台,那么为了在壮年期做获得我的 CREATION,我选择了加入阿里这样的大平台。</p>\n\n<p>在最后做决定以及初来阿里的那个人生转折点,作为老阿里人的曲洋老师对我说的一句话,深深地鼓励了我,他说:”带着创业气质,把这里当你的舞台折腾!”</p>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-2.png\" alt=\"image\" /></p>\n\n<p>注:2020 年双十一 · 淘宝 KO</p>\n\n<h3 id=\"最喜欢新六脉的哪句话为什么\">最喜欢新六脉的哪句话?为什么?</h3>\n\n<p>最喜欢的是“因为信任所以简单”。</p>\n\n<p>我一直认为人最重要的两个元特质是”真实”和“谦逊”,由”真实”可以塑造自我(对内)、构建信任(对外),后者可以带来清晰的边界,继而实现人与人之间高效的互动(这种互动包括各种人际关系在内,如婚姻、合伙、共事、合作等等)。</p>\n\n<p><strong>事物(虚实皆可)呈现在人的认知中,会得到三方面的投影:facts、opinion、feeling。如果我们足够真实,当我们需要把这三方面呈现给他人时,双方就能顺畅建立信任。信任的结果,就对应到这三方面:彼此之间建立共识(facts)、求同存异(opinion)、尊重感受(feeling),这就是”简单”。</strong></p>\n\n<p>另外一句是鼓励自己勇于绽放的一条:「此次此刻,非我莫属」。</p>\n\n<p>激情、自信、积极…… 通常行为统一表现为“勇于绽放自己”,绽放有表达(语言)与投身(行为)两种表现形式。更进一步推进就是”此次此刻,非我莫属”的阿里价值观。</p>\n\n<p>低调、稳重、谦逊,其实与“此次此刻,非我莫属“,并不矛盾。这点是我来到阿里后,发现自己在过去这些年的创业中已经不知不觉改变了,从 Introvert 逐渐变成了 Extrovert 的人,而且从曾经 social 中消耗能量,逐渐变为我现在可以感知到获得能量。这种变化,是我最近来阿里才确认发生的,此前因为自己创业者的身份没有察觉这种变化的发生。</p>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-3.png\" alt=\"image\" /></p>\n\n<p>注:2021 年秋 · 径山之行</p>\n\n<h3 id=\"关于阿里企业价值观为什么要接受这套价值观\">关于阿里企业价值观:为什么要接受这套价值观?</h3>\n\n<p>马老师和老逍都提到这个:我们是寻找同路人,而不是教育别人。这其实非常明晰地解释了为什么阿里要构建一个毛细血管网络一样的政委体系。基于这种用人理念,政委体系不敢说是最优解,但一定是优解(而且是否有更优解的论证没有意义)。</p>\n\n<p>对于个人,我的理解是要做两件事:<strong>1)构建自己的价值观体系(初始化);2)寻找价值观契合的公司(做匹配)。这两点里,没有任何地方提到”你要改变价值观为了契合你所在的公司”。</strong></p>\n\n<p>而企业价值观呢,其实可以分两部分看待:普世价值观、独特价值观。前者因为是普世的,所以到了哪个公司这种价值观都对,这点老逍也提了,比如“客户第一”。后者是个性化的,但不存在孰高孰低,就像一个人内向还是外向,你不能说哪个是错的。</p>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-4.png\" alt=\"image\" /></p>\n\n<p>注:2021 年双十一 · 天天特卖团队</p>\n\n<h3 id=\"价值观的本质意义极度务实视角是什么\">价值观的本质意义(极度务实视角)是什么?</h3>\n\n<p><strong>在充分考虑价值观适配使命、愿景基础上,价值观本身的意义,在和风细雨时(即企业价值观与其他价值判断相 match 时),是看不到的。但在暴风骤雨时(即企业价值观与其他价值判断相冲突时),就能显示其实实在在的作用了。</strong>我认为包括三类,前两个是阿里整体视角,第三个是阿里内部:</p>\n\n<ul>\n <li>经济体内,阿里与其他生态位的冲突或损益关系,如曾经的美蘑口一役。</li>\n <li>经济体内,其他的生态位之间的冲突或损益关系,如曾经的十月围城。</li>\n <li>阿里人的行为价值判断,如最近的钉钉代考事件、过往的各类廉政事件。</li>\n</ul>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-5.png\" alt=\"image\" /></p>\n\n<p>注:2021 年冬 · 淘宝天猫合并前合影</p>\n\n<h3 id=\"landing的sop\">Landing 的 SOP</h3>\n\n<p>大家都说 landing 充满挑战,马老师其实给出了 landing 的 SOP 三部曲:<strong>一起打过仗、一起创过新、一起度过难。三个经历都 close 才算 smooth landing。</strong></p>\n\n<p>集团人才策略层面、HR 实操层面、Leader 层面、,对于新人 landing 能做到什么程度的保障,其实每个新人感受到的不尽相同:</p>\n\n<ul>\n <li>集团层面,始终是在构建更好的新人 landing 环境的,这符合自身价值,这能打下很好的底子。</li>\n <li>实操层面,包括面试阶段对候选人的价值观判断、预期管理,面试及入职后公司文化及人才体系的事实呈现、内化吸收和长期解惑。</li>\n <li>Leader 层面,这是新人体感最强烈的部分,也是最重要的部分。尽管拥抱变化,但首先 Leader 需要给出尽可能最全面的考虑,其次是对候选人的预期管理。好的 Leader 会给候选人提供合理的着陆点、多个降落伞、缓冲垫,完成 smooth landing。</li>\n</ul>\n\n<p><img src=\"/img/src/2020-06-11-captain-alibaba-6.png\" alt=\"image\" /></p>\n\n<p>注:2021 年夏 · 出差厦漳泉</p>\n\n<h3 id=\"问问自己来到阿里如果初期我可能需要做一点改变那会是什么\">问问自己,来到阿里,如果初期我可能需要做一点改变,那会是什么?</h3>\n\n<p>曾经个人的激情与动力,常来自于“增长”。常说<strong>高增长掩盖一切</strong>,所以未来在阿里如果不能如创业般快速获得反馈得到积极结果,并且大平台中必然要接受大量关联方共同参与项目而导致的效率降低,因此我要逐渐改变自己,重新适应这种环境下的激情与动力获得方式。</p>\n\n<p>另一方面,作为创业公司的负责人,工作中鲜有因为内部原因而无法推进的事情,但是扮演肩部或腰部角色时,需要接受头部决策的一定程度不可控,这是我需要作出的适应与改变。关于这一点,我在几个月前就已经在做预期管理和心态调整,我认为以创业者的强适应性,这可能并不会是问题,但是我习惯于保持谨慎的乐观来面对自己。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>又是一年 Birthday!</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>又是一年 Birthday!</h2>\t\t\n\t<time datetime=\"2022-07-29T15:53:57+00:00\" class=\"by-line\">29 Jul 2022, 青岛 | 麦克船长 | 总计 0 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-07-27-captain-birthday-1.png\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>欢迎成为「淘宝-天天特卖」团队的创业合伙人!</title>\n \t<meta name=\"description\" content=\"阿里内部创业项目「天天特卖」招合伙人啦!以「特卖合伙人」为基石的、以「使众人行」的战友感为人才基本要求、以「用人做事,而非做事用人」为人才建设核心,是天天特卖团队的组织管理理念。天天特卖期待你的加入!\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>欢迎成为「淘宝-天天特卖」团队的创业合伙人!</h2>\t\t\n\t<time datetime=\"2021-11-11T19:59:43+00:00\" class=\"by-line\">11 Nov 2021, 杭州 | 麦克船长 | 总计 917 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2021-11-11-captain-tttm-1.jpg\" alt=\"imagee\" /></p>\n\n<h3 id=\"天天特卖团队理念\">天天特卖团队理念</h3>\n\n<h4 id=\"特卖合伙人\">特卖合伙人</h4>\n\n<p>以「特卖合伙人」为基石的、以「使众人行」的战友感为人才基本要求、以「用人做事,而非做事用人」为人才建设核心,是天天特卖团队的组织管理理念。特卖核心管理团队每 Q 会进行一次班子建设通晒。</p>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-9.jpg\" alt=\"imagee\" /></p>\n\n<h4 id=\"如何理解协作\">如何理解协作?</h4>\n\n<p>从长时间线来看,我们是为了不断积累信用,像一张信用卡一样,不断获得别人愿意支持我们的更大额度。不要事情结果还可以,而我们却没有积累到信用。互联网本质也是现代工业。而现代工业,一是社会分工,二是社会协作。想取得现代工业项目的结果,就要有更大的人才包容度、环境包容度。工作的结果就是在妥协与博弈中取得的,这是和光同尘的本质,也是现代工业复杂系统拿到结果的本质。只有这样我们才能让越来越多的人追随我们一起 do something,这种追随不一定只有上下级才是,而是愿意并且相信和我们能到达更远的地方。这背后的信用,要我们一步一个脚印地去积累,对他人给予的信任要保持敬畏、如履薄冰、懂得感恩。对每一段阶段性或长或短结束的合作,都要表达感谢。</p>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-8.jpg\" alt=\"imagee\" /></p>\n\n<h4 id=\"如何看待同学的优势及短板\">如何看待同学的优势及短板?</h4>\n\n<ul>\n <li>优势:讲优势有两个可能的目的,要么组织会在未来任务分配上重点考虑发挥该同学优势的事情,要告诉 TA,要激励 TA,是 TA 前行的自信来源之一。要么是对于同学也把握不准的特点,我们明确告诉 TA 这是你被我欣赏的优点。</li>\n <li>短板:什么是要讲的短板?未来一段时间,最期待你补足提升的。一旦这方面显著进步,就会向上迈进很大一步,甚至可以突破自己当下成长的瓶颈。要花多少篇幅讲?要比优势,有更大篇幅去讲。讲完就结束了么?对这个短板,一定要表达态度,也一定要对是否有方法、什么方法来补足短板要和同学沟通。</li>\n <li>无论是优势,还是短板,要说到点儿上,不要说片儿汤话。要让同学们能够引起思考、启发的。</li>\n</ul>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-10.jpg\" alt=\"imagee\" /></p>\n\n<h3 id=\"天天特卖期待你的加入\">天天特卖期待你的加入!</h3>\n\n<p>新天天特卖缘起于「手淘下沉市场战役)」,于 2021 年初上线,以「极致性价比货源、裸价直降、全网比价、买贵必赔」打造手淘极致价格敏感人群的购物阵地。目前天天特卖团队有行业运营、用户运营、数据策略、整合营销、直播运营、内容运营等岗位,有兴趣的同学可以钉钉随时找我,期待你的加入!</p>\n\n<h4 id=\"欢迎添加我的微信sinosuperman-推荐自荐--\">欢迎添加我的微信:sinosuperman 推荐、自荐 ^ ^</h4>\n\n<p><img src=\"/img/src/2021-11-11-captain-tttm-11.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-2.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-3.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-4.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-5.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-6.jpg\" alt=\"imagee\" />\n<img src=\"/img/src/2021-11-11-captain-tttm-7.jpg\" alt=\"imagee\" /></p>\n\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的阿里一年香(入职阿里一周年)</title>\n \t<meta name=\"description\" content=\"本文记录了麦克船长来到阿里巴巴集团整整一年时,麦克船长的主管给的寄语。考虑到公司商业敏感问题,做了一定的删节。现记录于此,用于以后的回顾。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的阿里一年香(入职阿里一周年)</h2>\t\t\n\t<time datetime=\"2021-06-04T15:42:43+00:00\" class=\"by-line\">04 Jun 2021, 杭州 | 麦克船长 | 总计 306 字</time>\n\t<div class=\"content\">\n\t\t<p>To 麦克船长</p>\n\n<p>1 周年快乐!很开心我们有这样一段共事的机会,虽开始时有些许波折,但随着进一步相处,我们很快能做到彼此欣赏、英雄相惜、默契配合,也特别感谢你对我的信任和支持,这是一切共事的基础。你强大的自驱力、脑力、对新事物的理解学习能力,都是最近几手新人里比较突出的。特别钦佩于你的执着和初性,对一件事认定后,迸发出的强大战斗力和决心。今天特卖这个新业务需要扎下根基,还真的需要一些舍我其谁的胆魄和更为犀利的突破,我也相信「新特卖」能成为你在阿里又一代表作,我希望我们的团队能为之骄傲和自豪,我们能不负公司所托,真正在下沉市场这场硬仗上有所建树,井取得令我们自己感到骄傲的突破,一起加油。</p>\n\n<p>From 麦克船长的主管</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>担任淘宝产品总负责人的双十一,是怎样的体验?</title>\n \t<meta name=\"description\" content=\"本文记录了一些影像,是关于麦克船长来到阿里巴巴集团的第一个双十一,负责担任淘宝的总PD(产品总负责人)。一年一度的双十一成了淘宝,乃至整个阿里集团的传统,就像阿里这家公司的春节过年一样,气氛热烈,而且消费者和商家朋友们也都会跟我们一同迎来一次购物与销售的狂欢。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>担任淘宝产品总负责人的双十一,是怎样的体验?</h2>\t\t\n\t<time datetime=\"2020-11-11T15:59:43+00:00\" class=\"by-line\">11 Nov 2020, 杭州 | 麦克船长 | 总计 138 字</time>\n\t<div class=\"content\">\n\t\t<p>说是体验,其实本文只记录了一些影像,是关于麦克船长来到阿里巴巴集团的第一个双十一,负责担任淘宝的总PD(产品总负责人)。一年一度的双十一成了淘宝,乃至整个阿里集团的传统,就像阿里这家公司的春节过年一样,气氛热烈,而且消费者和商家朋友们也都会跟我们一同迎来一次购物与销售的狂欢。</p>\n\n<p><img src=\"/img/src/2020-11-11-captain-double-eleven-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-2.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-3.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-4.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-5.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-6.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-7.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-8.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-9.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-10.jpg\" alt=\"image\" />\n<img src=\"/img/src/2020-11-11-captain-double-eleven-11.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>疫后怎么做餐饮品牌?三叉戟模式或成标配</title>\n \t<meta name=\"description\" content=\"2020 新型冠状病毒疫情,给所有商业领域都带来了巨大影响,而餐饮业可以说是首当其冲,但这同时也带来了很多多元化经营的启示。我们回归原点,餐饮业解决了我们什么需求?吃饭。但是当我们不选择去饭店就餐时,我们如何解决吃饭问题?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>疫后怎么做餐饮品牌?三叉戟模式或成标配</h2>\t\t\n\t<time datetime=\"2020-04-14T16:42:43+00:00\" class=\"by-line\">14 Apr 2020, 杭州 | 麦克船长 | 总计 1845 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<p><img src=\"/img/src/2020-04-15-covid2019-catering-business-mode-1.jpg\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n\n<p>2020 新型冠状病毒疫情,给所有商业领域都带来了巨大影响,而餐饮业可以说是首当其冲,但这同时也带来了很多多元化经营的启示。</p>\n\n<p>我们回归原点,餐饮业解决了我们什么需求?吃饭。但是当我们不选择去饭店就餐时,我们如何解决吃饭问题?</p>\n\n<ul>\n <li>外卖</li>\n <li>做饭</li>\n <li>速食</li>\n</ul>\n\n<p>而这三方面,恰恰就是餐饮企业多元化经营的答案。</p>\n\n<h3 id=\"标品化外卖业务\">标品化外卖业务</h3>\n\n<p>一些以堂食为主的大餐饮品牌,比如海底捞、太二酸菜鱼、呷哺呷哺、西贝莜面村等等,应该更加重视外卖的价值了。重视到什么程度?比如这次疫情的影响,让你的成本与收入结构决定你只能关店,那说明你的外卖业务体量仍太小,过度依赖于堂食营收。</p>\n\n<p>其实大品牌做外卖,具有先天优势:信任度、定价优势。</p>\n\n<p>将门店部分菜品做标准化,设定外卖菜单,将外卖业务作为门店的重要多元化经营手段之一:</p>\n\n<ul>\n <li>形成<strong>场景互补</strong>,可以<strong>增强抗风险能力</strong>,除了这次的瘟疫,其他很多情况都会导致消费者外储减少,进而出现区域性的门店营收下降,比如台风、雾霾、暴雨等等。</li>\n <li><strong>增加品牌露出渠道</strong>。门店模式,以线下地段的人流曝光、点评等「到店」为主的互联网平台曝光为主,而外卖可以带来「到家」为主的互联网平台曝光。</li>\n</ul>\n\n<h3 id=\"社区生鲜前置仓\">社区生鲜前置仓</h3>\n\n<p>数据显示,京东生鲜配送到家业务相对节前环比增长 370%,叮咚买菜大年三十的订单量同比上月增长超过 300%;美团买菜在北京地区的日订单量达到了春节节前单量的 2-3 倍;除夕至初四,每日优鲜平台实收交易额较去年同期增长 321%。</p>\n\n<p>而餐饮门店,先天性地就需要大量采购生鲜食材、调味品,而采购量如果不合理,还会出现库存积压甚至损失的问题。如果餐饮连锁品牌把每家门店本身,变为一个生鲜食材的社区前置仓,反而比生鲜电商更具有优势。</p>\n\n<p>从另一个角度说,叮咚买菜、美菜等生鲜电商平台,甚至美团,也可以寻求和某个或某几个门店数量较多、分布较匹配的餐饮连锁品牌合作,对于自己的市场扩张也是很大的助力,是一种双赢。</p>\n\n<p>这样看来,盒马鲜生最初尝试的「超市+堂食+生鲜配送」的模式,或成为最佳先行者案例。以购物为主业的物美、永辉、联华,其实都可以进化成这种模式,而以堂食为主业的餐饮门店,可以用更社区的方式,进化成这种模式。</p>\n\n<p>在这方面,餐饮企业应该发挥自身优势,避开短板。购物业态发展起来的生鲜配送,往往只能提供蔬菜禽蛋肉,而餐饮企业除此之外,还可以提供半成品食材,方便消费者进行简餐烹饪就可做出一道菜。</p>\n\n<p>总结下餐饮做社区生鲜前置仓的特点:</p>\n\n<ul>\n <li><strong>场景互补,增加收入模式,提升抗风险能力</strong></li>\n <li>培养消费者习惯,<strong>加深品牌认知</strong></li>\n <li><strong>加速库存周转</strong>,提升采购弹性</li>\n</ul>\n\n<h3 id=\"线上预包装食品\">线上预包装食品</h3>\n\n<p>从本次疫情的速食类预包装食品销售大幅增长来看,当人们无法外出就餐,也不想自己生火做饭时,速食预包装食品依然是最重要的就餐保底选择。</p>\n\n<p>大餐饮品牌非常适合拓展预包装食品,而且消费者认知里会觉得大餐饮品牌的预包装食品更有品质、更安全。这样就需要品牌选好关联品类,比如川菜、湘菜品牌,推出辣味食品就很符合消费者心智认知;新疆、内蒙的地方特色餐饮品牌,则可以提供牛羊肉类的预包装零食;海鲜类餐饮品牌,可以推出水产类零食,等等。</p>\n\n<p>提供预包装食品,会从四方面助力餐饮品牌发展:</p>\n\n<ul>\n <li><strong>场景互补,增加收入模式,提升抗风险能力</strong></li>\n <li><strong>更多渠道触达</strong>,原来传统餐饮品牌,在互联网领域最多触达到到店消费和外卖两个场景。增加预包装食品后,可以在众多电商平台曝光,并且进一步的增加抖音、快手、小红书、淘宝直播等自媒体种草与带货渠道,还可以在有赞、微盟支持先与公众号流量主合作。更进一步的还有社交电商、微商体系。</li>\n <li><strong>突破门店区域触达限制</strong>,对于预包装食品,只要快递能到达的范围,都是自己的客户覆盖区域。</li>\n <li>加深消费者认知,可以在一日三餐之外,有更多的场景唤起消费者。</li>\n</ul>\n\n<h3 id=\"线下重构新餐饮时代到来\">线下重构,新餐饮时代到来</h3>\n\n<p>危机也是发展的契机,这一次疫情必然带来线下全面的线下格局洗牌。</p>\n\n<ul>\n <li>用「标品化外卖」覆盖外卖场景</li>\n <li>用「生鲜前置仓」覆盖做饭场景</li>\n <li>用「预包装食品」覆盖速食场景</li>\n</ul>\n\n<p>而餐厅核心能力,为这三方面做供给支撑,这就是我们说的「<strong>三叉戟模式</strong>」。受翻台率限制的堂食则作为一个可选项,对客单价偏高的餐饮品牌,堂食依然占据重要意义,而低客单价的餐饮品牌或许三只尖刺的杀伤力会远超主柄。</p>\n\n<p>对于餐饮品牌来说,未来运用三叉戟模式,可能会成为一种常态。率先做出这种布局调整的餐饮品牌,会在线下流量重构的过程中,成为新餐饮时代的代表。</p>\n\n<p>最后还是想说一句,希望疫情早些结束,希望中国的餐饮企业们沉着应对,降成本、转模式都要趁早。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>延迟满足,才有自由</title>\n \t<meta name=\"description\" content=\"今天我们来聊聊延迟满足(Delayed Gratification)和即时满足(Instant Gratification)。面对不同的「对手」,我们要做到不同深度的延迟满足。而延迟满足的驻留时间,则量化了我们在相应深度上的延迟满足能力。有意培养,刻意练习,用延迟满足来帮助自我成长,是一个长期课题,我也在路上。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>延迟满足,才有自由</h2>\t\t\n\t<time datetime=\"2020-04-11T06:18:03+00:00\" class=\"by-line\">11 Apr 2020, 杭州 | 麦克船长 | 总计 4478 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<h3 id=\"写在前面\">写在前面</h3>\n\n<p>今天我们来聊聊延迟满足(Delayed Gratification)和即时满足(Instant Gratification)。</p>\n\n<h3 id=\"1儿童时期的延迟满足\">1、儿童时期的延迟满足</h3>\n\n<h4 id=\"棉花糖实验\">棉花糖实验</h4>\n\n<p>聊到延迟满足,就必须提到沃尔特·米歇尔教授(Walter Mischel)的「棉花糖实验」。在 1960 年时, 米歇尔教授对美国斯坦福大学宾恩幼儿园 600 名 4~6 岁的儿童进行了测试:小朋友们可以选择桌上的棉花糖、奥利奥饼干或椒盐脆饼,然后可以立即吃掉,或者等一会儿再吃。如果等一会儿的话,会得到奖励。</p>\n\n<p>研究发现不同的小朋友有不同的表现,平均延迟时间为 15~20 分钟,有些小朋友延迟时间很短,有的则比较长。而 20 多年以后米歇尔教授追踪实验当年的小朋友们,发现延迟时间与学业成功存在正相关性。</p>\n\n<p>而今天我想更多地从我自身的感受来看,在不同方面的延迟满足或即时满足,可能是幼儿时期就有的,也可以是成年后习得或改变的。但无论何时,我们都应该对此报以重视。</p>\n\n<p>先讲两个我自己的例子吧。</p>\n\n<h4 id=\"黑加仑零食\">黑加仑零食</h4>\n\n<p>我有一个哥哥。在我还没有上学的时候,父母每隔一段时间会给我们购买很多零食,买什么也是我们自己决定的。回到家里,我和哥哥会共同决策,给这些零食按照好吃程度排个序。其中没吃过的零食,就靠我们推测。排好之后,倒序来吃,也就是先从好吃程度最低的零食开始。一直印象很深刻,有一个黑加仑口味的零食,在某次排序中,拔得头筹,但是最后发现它并没有想象中好吃。</p>\n\n<h4 id=\"暑假作业\">暑假作业</h4>\n\n<p>另一个故事,也是来自于我和我哥哥,不过不同于上一个例子,这次我们俩有了不一样的表现。</p>\n\n<p>记得刚上小学的时候,寒暑假里,我都是先把所有作业做完,再开始玩的。尤其印象深刻的是,炎热的夏天里,我坐着小板凳,在床边写暑假作业,非常有画面感。而忘记是从哪年开始,哥哥在假期开始后,带着我先玩,我才跟他学会了「原来还可以先玩」。</p>\n\n<h4 id=\"延迟满足的背后是意志力自控力\">延迟满足的背后,是意志力/自控力</h4>\n\n<p>「黑加仑零食」和「暑假作业」都是我很小的时候,延迟满足的例子,而且基本来自于主动选择延迟满足。</p>\n\n<p>从相对没有那么好吃的零食开始,一直吃到最好吃的零食,整个过程的体验,是越来越美妙,充满期待的。而如果反过来,则会对后面的零食,越来越没有兴趣。最后结果就是某些零食,一直到过了保质期,也不会被吃掉。</p>\n\n<p>暑假里如果先玩个痛快,最后赶作业,其实前面玩的就不够放松。而如果先把作业做完,就可以彻彻底底地疯到开学,100% 的放松状态。</p>\n\n<p>如果考虑到婴幼儿时期的家庭教育,或有影响,那么也不能说延迟满足就是可以先天具备的。但是先天还是后天并不是我们讨论的重点,本文也不是学术论文。</p>\n\n<p>延迟满足的根本,其实是自控力/意志力。和前面提到的米歇尔教授一样,另一位来自斯坦福大学的心理学家凯利·麦格尼格尔(Kelly McGonigal),她有一本书叫《意志力》,也有译作《自控力》。按我的理解,自控力就像重力势能,从自控力很低的状态想爬坡到高自控力状态,需要克服很多,而反过来却轻而易举。</p>\n\n<p>因此对于自控力,需要长期的刻意练习形成,并且不能轻易打破。只有在高自控力下,才能形成对满足感的延迟有很强的掌控能力。</p>\n\n<p>那么问个更根本的问题:延迟满足,确定是好于即时满足吗?</p>\n\n<h3 id=\"2快乐理论挑战延迟满足\">2、快乐理论,挑战延迟满足</h3>\n\n<p>记得中学时,听老师讲过一个关于「烂苹果」的小故事,给我的延迟满足习惯带来了极大的挑战。</p>\n\n<h4 id=\"烂苹果\">烂苹果</h4>\n\n<p>有一个中国老太太,和一个某国老太太(忘记是哪国了),各买了一箱苹果。一开始都有个别几个,没那么新鲜,有一点点要烂了的样子。中国老太太比较会过日子,都是先挑烂的吃。而外国老太太会享受,先挑好的吃。</p>\n\n<p>于是,中国老太太几乎每次都在吃烂苹果,因为随着时间的推移,原来新鲜的也开始变烂了。而外国老太太,先把好苹果吃完了,那些一点点烂的苹果,后来就烂得很严重,干脆就扔了。</p>\n\n<p>结果就是,中国老太太吃了大量的烂苹果,而外国老太太虽然扔了一些苹果,但她吃掉的都是好苹果。由此引申说中国人在很多事情上都是在吃烂苹果。</p>\n\n<p>当时年纪小,对这类很 SB 的瞎编故事,还缺乏足够的反驳意识,尤其是这种中国、外国的夹杂民族自卑感的瞎编故事。但确实给我留下了深刻印象,以至于我会去想:是不是我周末回家,可以先打两天红色警戒和扫雷,周日晚上再赶作业?</p>\n\n<h4 id=\"快乐理论\">快乐理论</h4>\n\n<p>从「烂苹果」的故事里,我们可以引申出一个「快乐理论」。如果用快乐值,来衡量收益,并且认为苹果随着时间推移会逐渐变烂,那么先吃烂苹果的快乐值,始终与烂苹果关联。而先吃好苹果,最后的烂苹果全部扔掉,则快乐值都与好苹果关联。</p>\n\n<p>唯一区别是数量,比如可能前者吃了 10 个烂苹果,后者吃了 5 个好苹果。每次平均快乐收益,必然是前者更高。</p>\n\n<p>但这个理论角度对吗?</p>\n\n<h3 id=\"3我们应该在哪个范畴内讨论延迟满足\">3、我们应该在哪个范畴内讨论「延迟满足」?</h3>\n\n<p>休闲娱乐,和完成任务,是完全不同的范畴。对于前者来说,其实没什么好讨论的,我们应该关注与后者。</p>\n\n<p>完成任务,关联着学习、工作、创业等等。这些是我们人生过程的基石。所以「烂苹果」引出的「快乐理论」,我们没必要去深入讨论对错,起码从「范畴」上来看,就已经错误了。</p>\n\n<h4 id=\"仅有正反馈刺激的奶头乐\">仅有正反馈刺激的奶头乐</h4>\n\n<p>而「完成任务」并不像「吃」那么本能,它需要我们去克服困难、主动思考、建立方法,过程中会遭遇负反馈与正反馈。而吃,在大多数情况下,仅有正反馈。</p>\n\n<p>而仅有正反馈的事儿,如果我们持续做它,就会进入奶头乐的陷阱。典型的奶头乐,比如刷短视频、打游戏。因为它们有持续快速的正反馈,没有负反馈,所以它们本身就是「满足」,而我们应该做的是,在大尺度的讨论范畴中学会延迟它们。或者那些混杂正反馈与负反馈的事务,我们要学会延迟那些仅有正反馈的局部。</p>\n\n<h4 id=\"延迟满足重在顺序而非时间\">延迟满足重在顺序,而非时间</h4>\n\n<p>因此,我们讨论到这里就很明显发现,延迟满足的核心是「顺序」而非「时间」。而有的言论甚至愚昧地解读为「拖延」、「不能把握机会」,这就是理解错误了。</p>\n\n<p>将仅有正反馈、靠本能即可驱动的事情,优先级排低。而把存在负反馈但又很重要的事情,优先级排高。</p>\n\n<h3 id=\"4常见的延迟满足与即时满足\">4、常见的延迟满足与即时满足</h3>\n\n<h4 id=\"初阶对手vs浅度延迟满足\">初阶对手 vs 浅度延迟满足</h4>\n\n<p>常见的即时满足,需要我们去延迟的,有这些:</p>\n\n<ul>\n <li>\n <p>睡懒觉</p>\n </li>\n <li>\n <p>过量饮食</p>\n </li>\n <li>\n <p>玩游戏/刷短视频等手机、PC 娱乐内容</p>\n </li>\n <li>\n <p>冲动情绪</p>\n </li>\n <li>\n <p>炫耀(粗俗说就是装B)</p>\n </li>\n</ul>\n\n<p>……</p>\n\n<p>这些显而易见属于即时满足的事情,如果我们放着重要事情不做,而先干这些,其实我们会很自然地产生负罪感。所以这些事情只是初阶的延迟满足对手。</p>\n\n<p>初阶对手,基本都是完全处于我们本能,来满足我们的及时行乐。</p>\n\n<h4 id=\"中阶对手vs中度延迟满足\">中阶对手 vs 中度延迟满足</h4>\n\n<p>稍高级的对手,是那些并不那么明显的,比如下面这些。</p>\n\n<ul>\n <li>\n <p>在家边看电视/视频,边学习/工作</p>\n </li>\n <li>\n <p>依赖二手知识,而怠于一手知识</p>\n </li>\n <li>\n <p>刷知乎、行业资讯 APP</p>\n </li>\n</ul>\n\n<p>……</p>\n\n<p>例子会有很多,我们仅稍微说下这三个。</p>\n\n<p>我们来看看「在家边看电视/视频,边学习/工作」。你确实在学习/工作,但是你的专注度会极大的降低。而事后你回顾,还很可能认为「我学习了一个下午啊」,其实是你看了一个下午视频,捎带着学习/工作了一下。</p>\n\n<p>再来看第二个,什么是「二手知识」呢?就是那些把严重简化、略化的专业知识或内容,比如《10 分钟读懂 XXX》,比如用网红化的视频来讲一个原本需要下功夫的专业内容。看罗振宇《时间的朋友》跨年演讲、参加混沌大学的班级学习、报名朋友圈裂变分享的学习课程…… 这些都是二手知识。二手知识的危害是,你缺乏系统性学习与思考,而把学习和思考给综艺化、娱乐化、浅显化了。</p>\n\n<p>刷知乎,会给你一种错觉:你也是精英人群(起码和他们混一个社区的)。刷行业资讯 APP(比如 36 氪、虎嗅这类),也会给你一种错觉:你也是创投达人、专业人士(起码是跟他们混在同一个平台的)。这些错觉,会让你对自我身份认知出现偏差,获得一种虚无的满足感。</p>\n\n<h4 id=\"高阶对手vs深度延迟满足\">高阶对手 vs 深度延迟满足</h4>\n\n<p>有些延迟满足,是更深层、更高阶的。面对的对手,也是更高阶的。</p>\n\n<p>高阶对手的特点,是伪装成很正确的样子,实则对于已经进阶到高阶排位赛的你来说,会是更危险的敌人。</p>\n\n<p>我这里就说一个影响很大的:</p>\n\n<p>* 急于行动</p>\n\n<p>其实当代年轻人,勤奋程度大多没有问题。但是过于勤奋,可能会形成急于行动的坏习惯。</p>\n\n<p>对于执行力差的人,「先干再说」、「Just Do It」是很好的激励口号。但是对于执行力已经很优秀的人,「三思而后行」反而成了更重要的事。</p>\n\n<p>对于行动力强的人来说,行动本身甚至可以给他带来快感。而这快感,恰恰就是一种「正反馈的满足感」,如果形成了对这种满足感的过度依赖,那么「行动」本身,就成了我们应该着力去延迟的满足,以避免思考上的「武断」,或行动上的「鲁莽」。</p>\n\n<p>我们常听到的「战术的勤奋掩盖战略的懒惰」、「纸上得来终觉浅,绝知此事要躬行」,这些激励式的口号,都容易让我们变为「急于行动」的暴躁老哥。</p>\n\n<p>但是这两者也要注意平衡,如果矫枉过正,又会变成「过于纸上谈兵」的人。</p>\n\n<h3 id=\"5延迟满足的驻留时长\">5、延迟满足的驻留时长</h3>\n\n<p>延迟满足,并不是一个「是否」的选择问题,而是一个需量化衡量的能力指标。延迟满足能力的量化,就是延迟的驻留时长。</p>\n\n<p>你在延迟驻留多久以后,就需要给予正反馈满足了?比如我们常看到「你再写完一页,我就让你玩 10 分钟」这样的家长教育小朋友的场景。这里「一页」就意味着一个驻留时长。</p>\n\n<p>对于延迟满足能力强的人,也就是驻留时间非常长的人,他可能把全部工作全做完,甚至还能反复修改检查、打补丁、升级,再进入到正反馈满足阶段(休息或做其他事情)。而驻留时间短的人,可能做了 3 个小时,完成了一部分,他就要休息一下,娱乐一下。</p>\n\n<p>这是表层的差别,更深层的差别在于心理层面。比如有的人对于情绪释放这种事儿,我曾经认识一个 19 岁当上大酒店的大堂经理的人,老板对其他员工宣传他 23 岁,是为了帮助他树立威信。而这个大堂经理,每隔一段时间就会到老板办公室来,跟老板发泄做情绪释放,而其他员工看到的,一直是一个没有情绪化的大堂经理。</p>\n\n<p>这里面就体现出了延迟满足的驻留时间,以及驻留结束后的满足获得(情绪释放)。我们很难做到无限长,所以选择合理的满足获得方式,很重要。</p>\n\n<p>而有的人,则能完全化解于无形,无论在商业谈判中的挑衅,还是下属的忤逆犯上,还是陌生人的出言不逊,他都能很好的自控、消解,这种人的驻留时间,某种意义上已经达到无限长了,也就是在某件事儿的延迟满足上已经做到完全内化了。</p>\n\n<p>其他方面也与情绪控制类似,能将长驻留的延迟满足内化为习性,对自由就会有更大的掌控。自由是什么呢?就是我们常听到的那句「自由不是你想做什么就做什么,而是你不想做什么就不做什么」。而不想做什么就不做,除了面对外部的选择权,更多是面对自己的,而这恰恰就是长驻留的延迟满足。</p>\n\n<h3 id=\"后记\">后记</h3>\n\n<p>总结一下,面对不同的「对手」,我们要做到不同深度的延迟满足。而延迟满足的驻留时间,则量化了我们在相应深度上的延迟满足能力。有意培养,刻意练习,用延迟满足来帮助自我成长,是一个长期课题,我也在路上。</p>\n\n<p>对于延迟满足的实操,如果日后我有更多梳理,会再行文。也期待与朋友们的讨论,欢迎添加我的个人微信号:sinosuperman 。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>未来人工智能就是要:让普通人过上现在富豪们的生活</title>\n \t<meta name=\"description\" content=\"有很多领域,需要专业人士面对具体的问题,给出个性化的解决方案。想获取这些个性化的解决方案,就要用金钱作为交换代价。而人工智能(Artificial Intelligence)真正能够发挥巨大作用的,恰恰就是这些领域。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>未来人工智能就是要:让普通人过上现在富豪们的生活</h2>\t\t\n\t<time datetime=\"2017-02-23T18:23:33+00:00\" class=\"by-line\">23 Feb 2017, 北京 | 麦克船长 | 总计 627 字</time>\n\t<div class=\"content\">\n\t\t<p>如果我很有钱,我就会雇佣一名私人旅行助理,帮我制定旅行计划,购买叫票,预定酒店,打包行李。</p>\n\n<p>如果我很有钱,我就会雇佣一名私人造型师,帮我购买服饰鞋帽,安排各项活动应该的着装。</p>\n\n<p>如果我很有钱,我就会雇佣一名私人医生,关注我的健康状态,处理和解答一切日常医护问题。</p>\n\n<p>类似的还有私人律师,私人营养师,私人教师……</p>\n\n<h4 id=\"但是我没有钱呢\">但是我没有钱呢?</h4>\n\n<p>有很多领域,需要专业人士面对具体的问题,给出个性化的解决方案。想获取这些个性化的解决方案,就要用金钱作为交换代价。</p>\n\n<p>而人工智能(Artificial Intelligence)真正能够发挥巨大作用的,恰恰就是这些领域。</p>\n\n<p>技术的边际成本趋于零,使得私人旅行助理、私人造型师、私人医生、私人律师、私人营养师、私人教师可以低成本、高效率地解决这些问题。</p>\n\n<p>这需要云端<strong>计算能力</strong>的支持,<strong>海量数据</strong>的支撑,<strong>算法模型</strong>的发展,和<strong>产品设计</strong>上的场景化。目前来看,旅游是较早发力做 AI 旅行定制的,其他领域也都在探索。</p>\n\n<p>AI 最先颠覆掉的,就是这些领域里的低级工种。比如律师事务所里负责文书整理工作的小律师、医院里负责病例整理的小护士… 这些工作因其特别符合计算机数据处理的口味,只要信息实现比特化、计算能力足够强,就能很高效的解决。</p>\n\n<p>更进一步,当更多维度的数据被完善,更人性化、场景化的产品细节被考虑到后,这些领域被 AI 提升生产力的现象会更进一步渗透。</p>\n\n<p>未来,将会有越来越多的工作不再需要人力去完成,现在我们可以先看看富豪们都雇了哪些助理,那都是科技行业工作者的机会。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>我们是应该「断舍离」还是「念念不忘,必有回响」</title>\n \t<meta name=\"description\" content=\"如果对某事、某人、某物的执念,会对我们的人生产生负反馈,我们就应该对此事、此人、此物「断舍离」;相反,如果是正反馈,则应该「念念不忘」。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>我们是应该「断舍离」还是「念念不忘,必有回响」</h2>\t\t\n\t<time datetime=\"2017-01-31T20:59:21+00:00\" class=\"by-line\">31 Jan 2017, 北京 | 麦克船长 | 总计 2577 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<h3 id=\"引子\">引子</h3>\n\n<p>我们夸夸其谈着自己的风光往事,好不热闹。隔壁的老王也在,他一手扶在桌上,一手在胸前比划,上嘴唇一碰下嘴唇,段子信手拈来,逗得我们捧腹不已,笑声绕廊。老韩也开始讲起了衡水的传说,每次都不重样儿。猛兄靠在门框上,笑得眼镜在鼻梁上乱颤。</p>\n\n<p>宿舍里,只有来自黄冈的老朱一人,默不作声,不时看两眼手中的书,不时又看着正在说话的人,被逗得也跟着笑起来。</p>\n\n<p>「嗨,我说老朱,讲讲你啊!」</p>\n\n<p>「我就不提啦。」</p>\n\n<p>「为什么啊?说说,说说!」</p>\n\n<p>「我不太想提高中这些事情。你们聊,我先出去一下哈。」</p>\n\n<p>「哎,真是的,行行行,你去吧…… 刚才咱们说到哪了?」</p>\n\n<p>那是十年前,我和我的大学同学们刚刚入学不久,在宿舍里一起回忆着各自高中的趣事,吹着高考高分的牛逼满天飞的日子。</p>\n\n<p>毕业后,老韩去中科院的北京某研究所读书,老王去了佐治亚(GalTech)读博士,猛兄去了伯克利(Berkeley)。老朱是我们这些人里最有出息的,他去了斯坦福(Stanford),研究火箭和空气动力学什么的。而我去了后来成为纳斯达克上市公司的华南某互联网公司。</p>\n\n<p>每个大学入学之初似乎都是这样,大家都是对高中那些事儿念念不忘,对高考的得与失记忆犹新。在多次聊天中,老朱对这类话题都打了哈哈,我就开始有些好奇。某次和老朱单独相处,聊了很久,聊开了后我问起了此事,老朱带着那么一点假正经的样子对我说:</p>\n\n<blockquote>\n <p>我给自己订了规矩,叫「不提三高」:高中、高考、高分。</p>\n</blockquote>\n\n<p>为什么?我想在读这篇文章的你,从标题可能猜到了一二。老朱解释了原因,用后来流行的话概括说,就是「<strong>断舍离</strong>」。</p>\n\n<h3 id=\"断舍离\">断舍离</h3>\n\n<p>「断舍离」一词,出自于日本作家山下英子所著的同名书籍《断 · 舍 · 离》,在此书出版后,这一词开始流行起来,那大概是在 2013 年。这词本意是指一种实操性很强的整理术。而整理术,则是带有浓重日本文化色彩的一种关于生活物品整理的方法论。</p>\n\n<p><img src=\"https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/ya2QnV41Kod8O4XB/img/725f80ab-4efc-4b06-8eeb-f29fe6afe50e.webp\" alt=\"image\" /></p>\n\n<p>进而,这种理念得以传播后,便超越了整理术的应用范畴,开始影响一些我们生活的其他方面。</p>\n\n<p>无论是老朱的「不提三高」,还是山下英子的「断舍离」,其隐含的本质都是,不要沉溺于过去。言谈,图一时口快,却把你带回过去,更囿于成败得失,而弱化了未来的规划和执行力。实物,载过往回忆,但使你常念旧日,且占据生活留白,则减少了放空的机会和轻松感。</p>\n\n<h3 id=\"念念不忘必有回响\">念念不忘,必有回响</h3>\n\n<p>与「断舍离」一词同样流行于 2013 年前后的,还有一句话,叫「念念不忘,必有回响」。这一句最早出自弘一法师的《晚晴集》。</p>\n\n<p><img src=\"https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/ya2QnV41Kod8O4XB/img/494aa186-be1b-4ee1-a2e2-8704c8434e50.webp\" alt=\"image\" /></p>\n\n<p>2013 年,王家卫导演的电影《一代宗师》中引用了这句话而使其广为传播。</p>\n\n<p>值得我们「念念不忘」的,必是让我们在未来的生活中更能感到或力量、或幸福、或希望的憧憬或回忆。历史能够成书万卷,都是来自我们一代代人对过去的念念不忘,无论是坚硬的国仇家恨,还是柔软的儿女情长;科技能够加速更迭,也都来自于人类天性的好奇心驱使,不断探究已知问题的未知边界。</p>\n\n<p>但我们有时会听到不同的声音:「不要活在过去」、「不要太执着」、「学会归零」、「学会放下」…… 还有列宁老师提醒我们「忘记过去,就意味着背叛」。</p>\n\n<p>那我们什么时候应该「断舍离」,什么时候又应该「念念不忘」?有没有什么具备实操价值的方法?</p>\n\n<h3 id=\"用正负反馈来判断何时何为\">用「正负反馈」来判断何时何为?</h3>\n\n<p>一个简单的办法,就是用「正负反馈」来判断。<strong>如果对某事、某人、某物的执念,会对我们的人生产生负反馈,我们就应该对此事、此人、此物「断舍离」;相反,如果是正反馈,则应该「念念不忘」。</strong></p>\n\n<p>「断舍离」最好的例子,一定是整理术。「扔东西」会给你带来极大的快感。克制自己的购物欲,不仅省钱,也会省空间,毕竟生活于现代社会的我们,空间是何其的有限。我们辛辛苦苦赚来一平方米要好几万块的房子,当然不是为了堆放那些「将来总会用到」而其实根本不会用到的废物的。</p>\n\n<p>生命总被分成不同的旅程:中学到大学,大学到社会,国内到国外,工作到跳槽,合租到独居,单身到结婚,二人世界到家庭生活,一线员工到部门领导 …… 每次不同的人生状态跨越后,我们都会调整自己的生活节奏和作息规律,我们也更应该调整自己的心理状态。</p>\n\n<p>荣耀加身时,对我们的激励已经得到最大化,不必担心你从那次成绩中吸取的信心还没有被完全消化,而要担心它在你的心中风头过劲,以至于令你慢下脚步。</p>\n\n<p><strong>能够取得连续成功的人,往往都懂得「断舍离」</strong>。</p>\n\n<p>Yin 是我曾经的一位合伙人,在中学时获得了「国际数学奥林匹克竞赛金牌」,后来又取得美国一所顶尖大学的博士学位,并在多年后拿到很多华人留学生羡慕的美国某大学终身教职(tenure)。Yin 对探索高深技术问题,有着天生的狂热,类似这样的人我还认识不少。他们并不见得都谦虚谨慎,有得也是狂放不已,但都会保持较高的自律,很少放任自己。</p>\n\n<p>在进入新的学校后,忘掉自己曾是「全校第一」;在来到新的公司后,忘掉自己是「核心骨干」。如果这时「念念不忘」,听到的回响,肯定不是什么锣鼓喧天,也不会是鞭炮齐鸣,而不过是自己的一个屁。</p>\n\n<p>再过一年半,将是我的一位至亲离开我二十年整的日子。坦率地说,我不知道在十二岁时经受这样的打击,与在三十二岁、四十二岁时经历,有何区别,尽管我希望这来的越晚越好。但那种悲痛,真的会在几年后转变为长久的生活力量。我们的每个亲友皆有生老病死,我想很多读者朋友会明白这种转变过程。而这转变前后,我们应该怎样调节自己呢?</p>\n\n<p>倘若亲人的离世,对你打击很大,此时刻意的忘却是很难的,相反在一段时间内你都需要进行积极的心理建设。但那段时间过后,你应该「断舍离」还是「念念不忘」?我相信大多数成年人能够处理好,一般我们会把这种「想念」转化为生活的动力,他们的离去会让我们懂得更多,比如珍惜,比如完成遗志。这都是对未来的「念念不忘,必有回响」,这也便是对生活的正反馈。</p>\n\n<p>所谓「化悲痛为力量」,具体操作起来的办法,则是在<strong>痛苦初期尽量「断舍离」,尽量转移注意,尽量寻找排解出口</strong>,无论这种痛苦是来自工作、家人,还是学业、朋友。而<strong>后期开始,化为力量,才能「必有回响」</strong>。</p>\n\n<h3 id=\"后记\">后记</h3>\n\n<p>此篇的成文之日是 2017 年 2 月 1 日,农历丁酉年正月初五。在新的一年里,我也要对过往做个整理,对未来做个规划。而读完本文,不论你身处何时何地,也可以在心中整理一下,看看对哪些事、哪些人、哪些物应该「断舍离」了,而又对哪些事、哪些人、哪些物应该「念念不忘」。</p>\n\n<p>然后,我们一起等待,那声回响。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>一名出色软件工程师的技术基本功:编程与工具</title>\n \t<meta name=\"description\" content=\"再过一个多月,我就毕业工作一年了。目前在广州的 YY 语音,是 Web YY 音视频媒体技术负责人,公司预计在下半年上市,我希望通过 Web 版 YY 能为用户更容易访问(免注册、免登陆)来拉动 YY 的 DAU(活跃用户人数)助力 YY 上市。夜深人静,写一些自己对于出色软件工程师技术基本功的理解。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>一名出色软件工程师的技术基本功:编程与工具</h2>\t\t\n\t<time datetime=\"2012-05-15T17:06:59+00:00\" class=\"by-line\">15 May 2012, 广州 | 麦克船长 | 总计 2132 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<h3 id=\"0写在前面\">0、写在前面</h3>\n\n<p>再过一个多月,我就毕业工作一年了。目前在广州的 YY 语音,是 Web YY 音视频媒体技术负责人,公司预计在下半年上市,我希望通过 Web 版 YY 能为用户更容易访问(免注册、免登陆)来拉动 YY 的 DAU(活跃用户人数)助力 YY 上市。夜深人静,写一些自己对于出色软件工程师技术基本功的理解。</p>\n\n<h3 id=\"1编程\">1、编程</h3>\n\n<p>首先至少精通一门高级语言(注意是精通),然后要熟悉额外的几门语言。举例来说:</p>\n\n<h4 id=\"如果你精通c语言\">如果你精通 C 语言</h4>\n\n<p>那么除了其语言标准之外,还要精通 Linux 平台的系统 API,以及一些常用的库,还有单元测试工具。当然,如果你需要精通 C 语言的话,应该是需要你经常做与操作系统直接接触的应用底层开发,或者编写一些基础库。</p>\n\n<h4 id=\"如果你精通c语言-1\">如果你精通 C++ 语言</h4>\n\n<p>那么除了 C++ 语言标准外,你应该还要精通 STL(虽然这已经纳入 C++ 标准,但是我还是要提两句),以及一些常用的库,比如 Boost、ACE、POCO 等。</p>\n\n<p>另外,精通 C/C++ 要求你必须要会用 GCC/G++、GDB、Makefile(整合 Makefile 的 CMake 等)/Scons 等等。</p>\n\n<h4 id=\"精通的关键还是针对语言核心来说的\">精通的关键,还是针对语言核心来说的。</h4>\n\n<p>第一,你要对这个语言的语法特性熟稔;</p>\n\n<p>第二,你要对这个语言的标准库的每个 API 熟稔;</p>\n\n<p>第三,你要能够熟练运用这门语言编写各种设计模式;</p>\n\n<p>第四,你能够运用你对这门语言的掌握,完成任意给定的编程任务。</p>\n\n<p>那么,其他额外要熟悉的语言,你要做到有的放矢,就是当你要进行某种开发的时候,你在这方面能够熟练使用这门语言。比如你可以用 PHP 熟练地进行 Web 开发,你可以用 Perl 熟练地处理文本,你可以用 Bash 熟练地编写脚本小工具。</p>\n\n<h4 id=\"与计算机网络的基础结构相关联的技术实现\">与计算机、网络的基础结构相关联的技术实现</h4>\n\n<p>除了这些呢,设计模式、异步 IO、进程与线程、网络编程也是你必须精通的。当然,你只要精通你所使用的语言的这些方面的就可以了。</p>\n\n<h3 id=\"2工具\">2、工具</h3>\n\n<p>对于工具有三个层面:</p>\n\n<p>第一,是熟练的使用一些工具。</p>\n\n<p>第二,是能够发现提高生产力的工具。</p>\n\n<p>第三,是能够在无可用工具时自己编写工具。</p>\n\n<p>那么都有哪些最最最基本的工具呢?</p>\n\n<h4 id=\"ideintegrateddevelopmentenvironment\">IDE(Integrated Development Environment)</h4>\n\n<p>第一自然是 IDE,这是程序员的武器。如果你是 Windows 下的 C/C++ 开发者,建议你使用 Visual Studio,不要小看它,如果你能够精通它,你也算是一个高手。如果你是 Mac 下的C/C++/Objective-C 开发者,可以选择 XCode、Eclipse,并配合 Vim/Emacs 使用。如果你是 Linux 下的开发者,可以使用 Vim/Emacs。</p>\n\n<h4 id=\"vcsversioncontrolsystem\">VCS(Version Control System)</h4>\n\n<p>VCS 可以分为两类,一类是 CVCS(Central VCS),另一类是 DVCS(Distributed VCS)。现在 CVCS 一般使用 SVN、CVS,DVCS 一般使用 Git、Mercurial(Hg)。至于 CVCS 和 DVCS 的区别,道地谁先进,我喜欢下面这段比喻:</p>\n\n<blockquote>\n <p>Once you understand the conceptual differences between CVS/SVN and Git, and then subsequently start to use Git, you may find it very difficult to go back. You should really start to experiment only if you think you’re going to migrate in the near future, because using Git is like watching TV in colour: once you’ve discovered it, it’s really difficult to go back to black & white.</p>\n</blockquote>\n\n<p>一旦你使用了 VCS,你就会接触到 Google Code、Github、BitBucket 等等。它们其实可以算是一种在线工具。</p>\n\n<h4 id=\"clicommandlineinterface\">CLI(Command Line Interface)</h4>\n\n<p>我们一般都说命令行(Command Line),为什么还带一个「I」呢?类比 API(Application Program Interface)、GUI(Graphical User Interface)就能明白了,这都是与某个系统的交互接口,API 是通过一些 Library 调用实现交互,GUI 是通过在图形界面上的点击/拖动/滑动等实现交互。熟练地运用操作系统的 CLI。无论你是使用 Linux、Mac、Solaris、FreeBSD,甚至是 Windows,你都要熟练使用 CLI。</p>\n\n<h3 id=\"3结语\">3、结语</h3>\n\n<p>还能想到什么?由于现在夜深人静,头脑不够清醒,只能想到这些。况且在这些方面,我也达不到「精通」,甚至想去甚远。那姑且先这样吧,如果哪位朋友有什么想说的,可以在下面给我留言,我会补充到文中。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"design":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 礼狮™ LISMIS™ 巧可宝 Chocobble [12P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 礼狮™ LISMIS™ 巧可宝 Chocobble [12P]</h2>\t\t\n\t<time datetime=\"2017-12-24T06:18:03+00:00\" class=\"by-line\">24 Dec 2017, 杭州 | 麦克船长 | 总计 382 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>产品:礼狮™ LISMIS™ 巧可宝 Chocobble</li>\n <li>产地:希腊</li>\n <li>麦克船长负责产品研发、总体设计(品牌/VI/包装/视觉)</li>\n</ul>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-001.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-002.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p><img src=\"/img/design/lismis-chocobble-003.jpg\" alt=\"image\" /></p>\n\n<p>Chocobble™ - jar x 4</p>\n\n<table>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-chocobble-4jar-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></td>\n </tr>\n </tbody>\n</table>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-4jar-2.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-4jar-3.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>Chocobble™ - jar x 8</p>\n\n<table>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-chocobble-8jar-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></td>\n </tr>\n </tbody>\n</table>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-8jar-2.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-8jar-3.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>Chocobble™ - jar x 15</p>\n\n<table>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-chocobble-15jar-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></td>\n </tr>\n </tbody>\n</table>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-chocobble-15jar-2.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-chocobble-15jar-3.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 礼狮™ LISMIS™ 盐焗腰果黑巧克力 Dark Chocolate Covered Cashew [3P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 礼狮™ LISMIS™ 盐焗腰果黑巧克力 Dark Chocolate Covered Cashew [3P]</h2>\t\t\n\t<time datetime=\"2017-12-24T06:18:03+00:00\" class=\"by-line\">24 Dec 2017, 杭州 | 麦克船长 | 总计 92 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>产品:礼狮™ LISMIS™ 盐焗腰果黑巧克力 Dark Chocolate Covered Cashew</li>\n <li>产地:美国</li>\n <li>麦克船长负责产品研发、总体设计(品牌/VI/包装/视觉)</li>\n</ul>\n\n<p><img src=\"/img/design/lismis-cube03-1.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<p><img src=\"/img/design/lismis-cube03-2.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<p><img src=\"/img/design/lismis-cube03-3.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 礼狮™ LISMIS™ 品牌 VI [9P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 礼狮™ LISMIS™ 品牌 VI [9P]</h2>\t\t\n\t<time datetime=\"2017-12-01T04:56:40+00:00\" class=\"by-line\">01 Dec 2017, 杭州 | 麦克船长 | 总计 144 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>礼狮™ LISMIS™ 品牌</li>\n <li>麦克船长负责产品研发、总体设计(品牌/VI/包装/视觉)</li>\n</ul>\n\n<p><img src=\"/img/design/lismis-vi-001.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-vi-003.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-vi-006.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><img src=\"/img/design/lismis-vi-005.jpg\" alt=\"image\" /></td>\n <td><img src=\"/img/design/lismis-vi-004.jpg\" alt=\"image\" /></td>\n </tr>\n </tbody>\n</table>\n\n<p><img src=\"/img/design/lismis-vi-002.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/design/lismis-vi-007.jpg\" alt=\"image\" /></th>\n <th><img src=\"/img/design/lismis-vi-008.jpg\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p><img src=\"/img/design/lismis-vi-009.jpg\" alt=\"image\" style=\"border:1px solid #EEEEEE\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | 游戏美术 Interstaller Colonial Agency [4P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | 游戏美术 Interstaller Colonial Agency [4P]</h2>\t\t\n\t<time datetime=\"2016-04-20T13:09:59+00:00\" class=\"by-line\">20 Apr 2016, 北京 | 麦克船长 | 总计 91 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>游戏名称:Interstaller Colonial Agency</li>\n <li>游戏类型:Turn-based Stategy Game</li>\n <li>发行平台:iOS</li>\n <li>策划&美术:麦克船长</li>\n</ul>\n\n<p><img src=\"/img/design/ica-001.png\" alt=\"image\" /></p>\n\n<p><img src=\"/img/design/ica-004.png\" alt=\"image\" /></p>\n\n<p><img src=\"/img/design/ica-002.png\" alt=\"image\" /></p>\n\n<p><img src=\"/img/design/ica-003.png\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>design | Club APP [12P]</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>design | Club APP [12P]</h2>\t\t\n\t<time datetime=\"2015-12-07T08:58:54+00:00\" class=\"by-line\">07 Dec 2015, 北京 | 麦克船长 | 总计 0 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/design/club-app-smartisan.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"],"ai":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>国家网信办《互联网信息服务深度合成管理规定》解读</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>国家网信办《互联网信息服务深度合成管理规定》解读</h2>\t\t\n\t<time datetime=\"2023-02-06T15:24:58+00:00\" class=\"by-line\">06 Feb 2023, 香港 | 麦克船长 | 总计 4347 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"国家互联网信息办公室中华人民共和国工业和信息化部中华人民共和国公安部-令-第12号\">国家互联网信息办公室、中华人民共和国工业和信息化部、中华人民共和国公安部 令 第12号</h2>\n\n<p>《互联网信息服务深度合成管理规定》已经2022年11月3日国家互联网信息办公室2022年第21次室务会议审议通过,并经工业和信息化部、公安部同意,现予公布,自2023年1月10日起施行。</p>\n\n<p>国家互联网信息办公室主任 庄荣文</p>\n\n<p>工业和信息化部部长 金壮龙</p>\n\n<p>公安部部长 王小洪</p>\n\n<p>2022年11月25日</p>\n\n<h2 id=\"互联网信息服务深度合成管理规定\">互联网信息服务深度合成管理规定</h2>\n\n<h3 id=\"第一章-总则\">第一章 总则</h3>\n\n<p>第一条 为了加强互联网信息服务深度合成管理,弘扬社会主义核心价值观,维护国家安全和社会公共利益,保护公民、法人和其他组织的合法权益,根据《中华人民共和国网络安全法》、《中华人民共和国数据安全法》、《中华人民共和国个人信息保护法》、《互联网信息服务管理办法》等法律、行政法规,制定本规定。</p>\n\n<p>第二条 在中华人民共和国境内应用深度合成技术提供互联网信息服务(以下简称深度合成服务),适用本规定。法律、行政法规另有规定的,依照其规定。</p>\n\n<p>第三条 国家网信部门负责统筹协调全国深度合成服务的治理和相关监督管理工作。国务院电信主管部门、公安部门依据各自职责负责深度合成服务的监督管理工作。</p>\n\n<p>地方网信部门负责统筹协调本行政区域内的深度合成服务的治理和相关监督管理工作。地方电信主管部门、公安部门依据各自职责负责本行政区域内的深度合成服务的监督管理工作。</p>\n\n<p>第四条 提供深度合成服务,应当遵守法律法规,尊重社会公德和伦理道德,坚持正确政治方向、舆论导向、价值取向,促进深度合成服务向上向善。</p>\n\n<p>第五条 鼓励相关行业组织加强行业自律,建立健全行业标准、行业准则和自律管理制度,督促指导深度合成服务提供者和技术支持者制定完善业务规范、依法开展业务和接受社会监督。</p>\n\n<h3 id=\"第二章-一般规定\">第二章 一般规定</h3>\n\n<p>第六条 任何组织和个人不得利用深度合成服务制作、复制、发布、传播法律、行政法规禁止的信息,不得利用深度合成服务从事危害国家安全和利益、损害国家形象、侵害社会公共利益、扰乱经济和社会秩序、侵犯他人合法权益等法律、行政法规禁止的活动。</p>\n\n<p><strong><u>深度合成服务提供者和使用者不得利用深度合成服务制作、复制、发布、传播虚假新闻信息。转载基于深度合成服务制作发布的新闻信息的,应当依法转载互联网新闻信息稿源单位发布的新闻信息</u>></strong>。</p>\n\n<p>第七条 深度合成服务提供者应当落实信息安全主体责任,建立健全用户注册、<strong><u>算法机制机理审核、科技伦理审查</u></strong>、信息发布审核、数据安全、个人信息保护、反电信网络诈骗、应急处置等管理制度,具有安全可控的技术保障措施。</p>\n\n<p>第八条 深度合成服务提供者应当制定和公开管理规则、平台公约,完善服务协议,依法依约履行管理责任,以显著方式提示深度合成服务技术支持者和使用者承担信息安全义务。</p>\n\n<p>第九条 深度合成服务提供者应当基于移动电话号码、身份证件号码、统一社会信用代码或者国家网络身份认证公共服务等方式,依法对深度合成服务使用者进行真实身份信息认证,<strong><u>不得向未进行真实身份信息认证的深度合成服务使用者提供信息发布服务</u></strong>。</p>\n\n<p>第十条 深度合成服务提供者应当加强深度合成内容管理,采取<strong><u>技术</u></strong>或者人工方式对深度合成服务使用者的输入数据和合成结果进行审核。</p>\n\n<p><strong><u>深度合成服务提供者应当建立健全用于识别违法和不良信息的特征库,完善入库标准、规则和程序,记录并留存相关网络日志</u></strong>。</p>\n\n<p>深度合成服务提供者发现违法和不良信息的,应当依法采取处置措施,保存有关记录,及时向网信部门和有关主管部门报告;对相关深度合成服务使用者依法依约采取警示、限制功能、暂停服务、关闭账号等处置措施。</p>\n\n<p>第十一条 <strong><u>深度合成服务提供者应当建立健全辟谣机制</u></strong>,发现利用深度合成服务制作、复制、发布、传播虚假信息的,应当及时采取辟谣措施,保存有关记录,并向网信部门和有关主管部门报告。</p>\n\n<p>第十二条 深度合成服务提供者应当设置便捷的用户申诉和公众投诉、举报入口,公布处理流程和反馈时限,及时受理、处理和反馈处理结果。</p>\n\n<p>第十三条 互联网应用商店等应用程序分发平台应当落实上架审核、日常管理、应急处置等安全管理责任,核验深度合成类应用程序的安全评估、备案等情况;对违反国家有关规定的,应当及时采取不予上架、警示、暂停服务或者下架等处置措施。</p>\n\n<blockquote>\n <p>麦克船长解读:第二章整体就是告诉我们一句话,所有开发、分发深度合成产品的组织或个人,包括生成式 AI 的 SaaS 服务商(比如百度文心大模型)、to C 型应用平台/商店(比如华为应用商店/小米应用商店/微信小程序等)等,必须实现 Moderation 能力。</p>\n</blockquote>\n\n<h3 id=\"第三章-数据和技术管理规范\">第三章 数据和技术管理规范</h3>\n\n<p>第十四条 深度合成服务提供者和技术支持者应当加强训练数据管理,采取必要措施保障训练数据安全;训练数据包含个人信息的,应当遵守个人信息保护的有关规定。</p>\n\n<p><strong><u>深度合成服务提供者和技术支持者提供人脸、人声等生物识别信息编辑功能的,应当提示深度合成服务使用者依法告知被编辑的个人,并取得其单独同意</u></strong>。</p>\n\n<p>第十五条 深度合成服务提供者和技术支持者应当加强技术管理,<strong><u>定期审核、评估、验证生成合成类算法机制机理</u></strong>。</p>\n\n<p>深度合成服务提供者和技术支持者提供具有以下功能的模型、模板等工具的,<strong><u>应当依法自行或者委托专业机构开展安全评估</u></strong>:</p>\n\n<p>(一)生成或者编辑人脸、人声等生物识别信息的;</p>\n\n<p>(二)生成或者编辑可能涉及国家安全、国家形象、国家利益和社会公共利益的特殊物体、场景等非生物识别信息的。</p>\n\n<p>第十六条 深度合成服务提供者对使用其服务生成或者编辑的信息内容,应当采取技术措施添加不影响用户使用的标识,并依照法律、行政法规和国家有关规定保存日志信息。</p>\n\n<p>第十七条 深度合成服务提供者提供以下深度合成服务,可能导致公众混淆或者误认的,应当<strong><u>在生成或者编辑的信息内容的合理位置、区域进行显著标识,向公众提示深度合成情况</u></strong>:</p>\n\n<p>(一)智能对话、智能写作等模拟自然人进行文本的生成或者编辑服务;</p>\n\n<p>(二)合成人声、仿声等语音生成或者显著改变个人身份特征的编辑服务;</p>\n\n<p>(三)人脸生成、人脸替换、人脸操控、姿态操控等人物图像、视频生成或者显著改变个人身份特征的编辑服务;</p>\n\n<p>(四)沉浸式拟真场景等生成或者编辑服务;</p>\n\n<p>(五)其他具有生成或者显著改变信息内容功能的服务。</p>\n\n<p>深度合成服务提供者提供前款规定之外的深度合成服务的,<strong><u>应当提供显著标识功能,并提示深度合成服务使用者可以进行显著标识</u></strong>。</p>\n\n<p>第十八条 任何组织和个人<strong><u>不得采用技术手段删除、篡改、隐匿本规定第十六条和第十七条规定的深度合成标识</u></strong>。</p>\n\n<blockquote>\n <p>麦克船长解读:1)必须有评估、验证合成算法机制;2)必须要有明确标识告诉使用者;3)生成类服务的范围,在第十七条的第五款里,用「其他」变成了一个什么都能装的筐。</p>\n</blockquote>\n\n<h3 id=\"第四章-监督检查与法律责任\">第四章 监督检查与法律责任</h3>\n\n<p>第十九条 <strong><u>具有舆论属性或者社会动员能力的深度合成服务提供者,应当按照《互联网信息服务算法推荐管理规定》履行备案和变更、注销备案手续</u></strong>。</p>\n\n<p>深度合成服务技术支持者应当参照前款规定履行备案和变更、注销备案手续。</p>\n\n<p>完成备案的深度合成服务提供者和技术支持者应当在其对外提供服务的网站、应用程序等的<strong><u>显著位置标明其备案编号并提供公示信息链接</u></strong>。</p>\n\n<blockquote>\n <p>麦克船长解读:有媒体舆论属性的产品(比如社交社区、直播短视频、新闻媒体类等),一定要按照《互联网信息服务算法推荐管理规定》备案,并在 APP、网站上公示备案编号并提供链接(类似之前的域名备案)。</p>\n</blockquote>\n\n<p>第二十条 深度合成服务提供者开发上线具有舆论属性或者社会动员能力的新产品、新应用、新功能的,应当按照国家有关规定开展安全评估。</p>\n\n<blockquote>\n <p>麦克船长解读:上述提到的产品迭代新能力时,也要再次安全评估。</p>\n</blockquote>\n\n<p>第二十一条 网信部门和电信主管部门、公安部门依据职责对深度合成服务开展监督检查。深度合成服务提供者和技术支持者应当依法予以配合,并提供必要的技术、数据等支持和协助。</p>\n\n<p>网信部门和有关主管部门发现深度合成服务存在较大信息安全风险的,可以按照职责依法要求深度合成服务提供者和技术支持者采取暂停信息更新、用户账号注册或者其他相关服务等措施。深度合成服务提供者和技术支持者应当按照要求采取措施,进行整改,消除隐患。</p>\n\n<blockquote>\n <p>麦克船长解读:网信部门、电信部门、公安部门都可以监管,都要配合。监管的整改可能会配合停止信息更新、停止账号注册等。这就跟食品生产企业的整改监管非常类似了,生产内容一样要被这样监管,有了合成能力本质上就是出现了内容工厂了。</p>\n</blockquote>\n\n<p>第二十二条 深度合成服务提供者和技术支持者违反本规定的,依照有关法律、行政法规的规定处罚;造成严重后果的,依法从重处罚。</p>\n\n<p>构成违反治安管理行为的,由公安机关依法给予治安管理处罚;构成犯罪的,依法追究刑事责任。</p>\n\n<h3 id=\"第五章-附则\">第五章 附则</h3>\n\n<p>第二十三条 本规定中下列用语的含义:</p>\n\n<p>深度合成技术,是指利用深度学习、虚拟现实等生成合成类算法制作文本、图像、音频、视频、虚拟场景等网络信息的技术,包括但不限于:</p>\n\n<p>(一)篇章生成、文本风格转换、问答对话等生成或者编辑文本内容的技术;</p>\n\n<p>(二)文本转语音、语音转换、语音属性编辑等生成或者编辑语音内容的技术;</p>\n\n<p>(三)音乐生成、场景声编辑等生成或者编辑非语音内容的技术;</p>\n\n<p>(四)人脸生成、人脸替换、人物属性编辑、人脸操控、姿态操控等生成或者编辑图像、视频内容中生物特征的技术;</p>\n\n<p>(五)图像生成、图像增强、图像修复等生成或者编辑图像、视频内容中非生物特征的技术;</p>\n\n<p>(六)三维重建、数字仿真等生成或者编辑数字人物、虚拟场景的技术。</p>\n\n<p>深度合成服务提供者,是指提供深度合成服务的组织、个人。</p>\n\n<p>深度合成服务技术支持者,是指为深度合成服务提供技术支持的组织、个人。</p>\n\n<p>深度合成服务使用者,是指使用深度合成服务制作、复制、发布、传播信息的组织、个人。</p>\n\n<p>训练数据,是指被用于训练机器学习模型的标注或者基准数据集。</p>\n\n<p>沉浸式拟真场景,是指应用深度合成技术生成或者编辑的、可供参与者体验或者互动的、具有高度真实感的虚拟场景。</p>\n\n<p>第二十四条 深度合成服务提供者和技术支持者从事网络出版服务、网络文化活动和网络视听节目服务的,应当同时符合新闻出版、文化和旅游、广播电视主管部门的规定。</p>\n\n<p>第二十五条 本规定自2023年1月10日起施行。</p>\n\n<blockquote>\n <p>麦克船长解读:不仅限于 AIGC,虚拟现实生成也被该规定覆盖。一些可能会想到的问题如下。</p>\n <ol>\n <li>比如我只用 AI 改改文章风格不用被监管吧?否,也按本规定监管。</li>\n <li>艺术类,非写实类的图像生成,不用被监管吧?否,也被本规定监管,不是只有写实的内容才有可能不合规。</li>\n <li>用 AI 构建 3D 模型,主要用于装饰、装修、装潢的,不用被监管吧?否,这个能力有了,就不限于生成范围了,会有涉及监管的应用,所以也要被监管。</li>\n </ol>\n</blockquote>\n\n<h2 id=\"参考\">参考:</h2>\n\n<ul>\n <li>http://www.cac.gov.cn/2022-12/11/c_1672221949354811.htm</li>\n <li>https://tisi.org/14419</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenAI 模型 API 官方文档入门解读</title>\n \t<meta name=\"description\" content=\"在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。本文麦克船长重点为你解读基于 GPT-3 的几款模型的入门使用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenAI 模型 API 官方文档入门解读</h2>\t\t\n\t<time datetime=\"2023-01-23T22:24:58+00:00\" class=\"by-line\">23 Jan 2023, 香港 | 麦克船长 | 总计 12873 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\" id=\"markdown-toc-一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</a> <ul>\n <li><a href=\"#1执行各种自然语言任务的-gpt-3\" id=\"markdown-toc-1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</a> <ul>\n <li><a href=\"#示例-1copywriting\" id=\"markdown-toc-示例-1copywriting\">示例 1:Copywriting</a></li>\n <li><a href=\"#示例-2summarization\" id=\"markdown-toc-示例-2summarization\">示例 2:Summarization</a></li>\n <li><a href=\"#示例-3parsing-unstructured-text\" id=\"markdown-toc-示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</a></li>\n <li><a href=\"#示例-4classification\" id=\"markdown-toc-示例-4classification\">示例 4:Classification</a></li>\n <li><a href=\"#示例-5translation\" id=\"markdown-toc-示例-5translation\">示例 5:Translation</a></li>\n </ul>\n </li>\n <li><a href=\"#2将自然语言翻译成代码的-codex\" id=\"markdown-toc-2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</a> <ul>\n <li><a href=\"#示例-6用自然语言写-sql\" id=\"markdown-toc-示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</a></li>\n <li><a href=\"#示例-7用自然语言调用一个-api\" id=\"markdown-toc-示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</a></li>\n <li><a href=\"#示例-8用自然语言续写代码\" id=\"markdown-toc-示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</a></li>\n </ul>\n </li>\n <li><a href=\"#3创建和编辑原始图像的-dalle\" id=\"markdown-toc-3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</a></li>\n </ul>\n </li>\n <li><a href=\"#二openai-api-总览性介绍\" id=\"markdown-toc-二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</a> <ul>\n <li><a href=\"#1一些关键概念\" id=\"markdown-toc-1一些关键概念\">1、一些关键概念</a></li>\n <li><a href=\"#2模型\" id=\"markdown-toc-2模型\">2、模型</a></li>\n </ul>\n </li>\n <li><a href=\"#三主要-api-介绍及代码示例\" id=\"markdown-toc-三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</a> <ul>\n <li><a href=\"#1text-completion-任务\" id=\"markdown-toc-1text-completion-任务\">1、Text Completion 任务</a></li>\n <li><a href=\"#2text-edit-任务\" id=\"markdown-toc-2text-edit-任务\">2、Text Edit 任务</a></li>\n <li><a href=\"#3image-create-任务beta\" id=\"markdown-toc-3image-create-任务beta\">3、Image Create 任务(Beta)</a></li>\n <li><a href=\"#4image-edit-任务\" id=\"markdown-toc-4image-edit-任务\">4、Image Edit 任务</a></li>\n <li><a href=\"#5审查moderation\" id=\"markdown-toc-5审查moderation\">5、审查(Moderation)</a></li>\n </ul>\n </li>\n <li><a href=\"#四精调fine-tuning\" id=\"markdown-toc-四精调fine-tuning\">四、精调(Fine-tuning)</a> <ul>\n <li><a href=\"#1创建一个-fine-tune-模型\" id=\"markdown-toc-1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</a></li>\n <li><a href=\"#2使用-fine-tuned-模型\" id=\"markdown-toc-2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</a></li>\n <li><a href=\"#3删掉一个-fine-tuned-模型\" id=\"markdown-toc-3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</a></li>\n <li><a href=\"#4一个-fine-tuned-模型之上继续-fine-tune\" id=\"markdown-toc-4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</a></li>\n </ul>\n </li>\n <li><a href=\"#参考\" id=\"markdown-toc-参考\">参考</a></li>\n</ul>\n\n<h2 id=\"一先大概看看-openai-目前最拿得出手的三个商业化产品用起来啥样\">一、先大概看看 OpenAI 目前最拿得出手的三个商业化产品用起来啥样</h2>\n\n<p>在 OpenAI 的 API 官方首页的大标题写着的是「用 OpenAI 的强力模型构建下一代应用(Build next-gen apps with OpenAI’s powerful models)」,并直接点出了自己最拿得出手的三个商业化产品 GPT-3、Codex、DALL·E。</p>\n\n<p>并提到自己的 API 调用非常简单:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"s\">\"davinci\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"Make a list of astronomical observatories:\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>引入 OpenAI 的 python 依赖库后,调用时指定用哪个引擎、提示词是什么即可。轻松一些,我们先来看一些示例吧。</p>\n\n<h3 id=\"1执行各种自然语言任务的-gpt-3\">1、执行各种自然语言任务的 GPT-3</h3>\n\n<h4 id=\"示例-1copywriting\">示例 1:Copywriting</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Create promo copy for the FamilyTime mobile application. It allows unlimited uploading, special filters and makes it easy to create albums of photos and videos. It runs on iOS and Android:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>FamilyTime is the perfect app to share and create memories with your family. FamilyTime is a free, unlimited photo and video editor that allows you to create albums of your memories. It has a special filter for the perfect summer look and is so simple to use.\n</code></pre></div></div>\n\n<h4 id=\"示例-2summarization\">示例 2:Summarization</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Summarize this email into a single sentence:\n\nDear Olivia,\n\nThe solar energy conference went great. New Horizon Manufacturing wants to meet with us to talk about our photovoltaic window system we’re about to launch.\nI think it would be great to talk this Tuesday.\n\nBest,\nAllison\n\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Allison is excited to meet with New Horizon Manufacturing to discuss their photovoltaic window system.\n</code></pre></div></div>\n\n<h4 id=\"示例-3parsing-unstructured-text\">示例 3:Parsing Unstructured Text</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Answer the following questions about this customer email:\n\nI’m wondering if you could provide me with information about your cloud services for companies. I’m CTO for Moon Door, a movie production company and want to know if it can help us manage our different projects that are in production.\n\nQuestions:\n1. What industry is the company\n2. What is the name of the company\n3. What are they interested in\n4. What is the customer’s position\n\nAnswers:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The company is in the movie production industry.\n2. The company is Moon Door.\n3. The customer is interested in cloud services for companies.\n4. The customer is the CTO.\n</code></pre></div></div>\n\n<h4 id=\"示例-4classification\">示例 4:Classification</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Choose a genre category for each book 1. The Hunger Games, 2. The Kite Runner 3. A Wrinkle in Time (“fiction”, “young adult”, “science fiction”, “fantasy”, “other”) and make a list of the book and its genre:\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>1. The Hunger Games: young adult, fiction\n2. The Kite Runner: fiction, young adult\n3. A Wrinkle in Time: science fiction, fantasy, other\n</code></pre></div></div>\n\n<h4 id=\"示例-5translation\">示例 5:Translation</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Translate this into French: Where can I find a bookstore?\n</code></pre></div></div>\n\n<p>GPT-3 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Où puis-je trouver un magasin de livres?\n</code></pre></div></div>\n\n<h3 id=\"2将自然语言翻译成代码的-codex\">2、将自然语言翻译成代码的 Codex</h3>\n\n<h4 id=\"示例-6用自然语言写-sql\">示例 6:用自然语言写 SQL</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Table customers, columns = [CustomerId, FirstName, LastName, Company, Address, City, State, Country, PostalCode, Phone, Fax, Email, SupportRepId, TotalSpend]\n\nCreate a MySQL query for all customers in Texas who have spent over five thousand dollars.\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>query = \"SELECT * FROM customers WHERE State = 'TX' AND TotalSpend > 5000\"\n</code></pre></div></div>\n\n<h4 id=\"示例-7用自然语言调用一个-api\">示例 7:用自然语言调用一个 API</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Python 3\nGet the current value of a Bitcoin in US dollars using the bitcoincharts api\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">requests</span>\n<span class=\"kn\">import</span> <span class=\"nn\">json</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">get_bitcoin_price</span><span class=\"p\">():</span>\n <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"s\">'http://api.bitcoincharts.com/v1/weighted_prices.json'</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json</span><span class=\"p\">.</span><span class=\"n\">loads</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span class=\"s\">'USD'</span><span class=\"p\">][</span><span class=\"s\">'7d'</span><span class=\"p\">]</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">get_bitcoin_price</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<h4 id=\"示例-8用自然语言续写代码\">示例 8:用自然语言续写代码</h4>\n\n<p>人类输入:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Extract all the html from the string and replace the tags with ''\n</code></pre></div></div>\n\n<p>Codex 输出:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>def extract_html(text):\n return re.sub('&lt;[^&lt;]+?&gt;', '', text)\n</code></pre></div></div>\n\n<h3 id=\"3创建和编辑原始图像的-dalle\">3、创建和编辑原始图像的 DALL·E</h3>\n\n<p>超过 300 万人已经在使用 DALL·E 来放大他们的创造力并加快他们的工作流程,每天生成超过 400 万张图像。</p>\n\n<h2 id=\"二openai-api-总览性介绍\">二、OpenAI API 总览性介绍</h2>\n\n<h3 id=\"1一些关键概念\">1、一些关键概念</h3>\n\n<p>关于 prompt 和 completion:OpenAI 提到一个理念:「设计提示语,就相当于在用一些指令和少量例子给模型编程」。另外 OpenAI 还强调了在目标任务上的区别,就是 OpenAI 的 NLP 模型与其他 NLP 模型很大的一个区别是,它不是设计用来解决单一类型任务的,而是可以解决几乎各种类型的 NLP 任务,包括但不限于文本生成(content generation)、代码生成(code generation)、总结(summarization)、扩写(expansion)、对话(conversation)、创意写作(creative wrting)、风格转换(style transfer)等。</p>\n\n<p>关于 token:我们理解和处理文本,是把文本先打碎成 token。以英文文本为例,token 可以是单词,也可以词根(一些字母组合),比如单词「hamburger」可能会被打碎成「ham」、「bur」、「ger」这几个 tokens。再比如「pear」这个单词,可能就会单独作为一个 token 不再打碎了。还有些 token 可能会以「空格」开头,比如「 hello」、「 bye」。一个大概的经验是,通常英文文本里 1 token 有 4 个字母或者 0.75 个单词。使用时的一个限制是,最好你的提示(prompt)或生成内容,不要超过 2048 个 tokens,大概相当于 1500 个单词。</p>\n\n<p>关于 model:目前 OpenAI 有这些基于 GPT-3 的基础模型 Davinci、Curie、Babbage、Ada 开放 API,另外 Codex 系列是 GPT-3 的后代,是用「自然语言 + 代码」训练的。</p>\n\n<h3 id=\"2模型\">2、模型</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">text-davinci-003</code>:最大请求 4000 tokens,训练数据 up to 2021 年 6 月,能做几乎所有 NLP 任务。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-curie-001</code>:最大请求 2048 tokens,训练数据 up to 2019 年 10 月,比 davinci 要弱一点,但是速度更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-babbage-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些比较直接的任务(straightforward tasks),比 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 更快、更便宜。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">text-ada-001</code>:最大请求和训练数据和 <code class=\"language-plaintext highlighter-rouge\">text-curie-001</code> 一样,一些非常简单的任务,这些模型里最快、最便宜的。</li>\n</ul>\n\n<p>这四个模型根据输入的 token 数量做的如下定价:</p>\n\n<ul>\n <li>基础模型使用 0.000<strong>4</strong> USD/1K tokens,Ada</li>\n <li>基础模型使用 0.000<strong>5</strong> USD/1K tokens,Babbage</li>\n <li>基础模型使用 0.00<strong>20</strong> USD/1K tokens,Curie</li>\n <li>基础模型使用 0.0<strong>200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>从定价上看,Ada 和 Babbage 基本没有差多少。另外命名上,可以看出 OpenAI 有意地给他们取了 ABCD 开头的名字。另外你也可以 finetune 你自己的模型,对于 fine-tuned models 如下收费:</p>\n\n<ul>\n <li>Finetune 训练费 0.000<strong>4</strong> USD/1K tokens,使用费 0.00<strong>16</strong> USD/1K tokens,Ada</li>\n <li>Finetune 训练费 0.000<strong>6</strong> USD/1K tokens,使用费 0.00<strong>24</strong> USD/1K tokens,Babbage</li>\n <li>Finetune 训练费 0.00<strong>30</strong> USD/1K tokens,使用费 0.0<strong>120</strong> USD/1K tokens,Curie</li>\n <li>Finetune 训练费 0.0<strong>300</strong> USD/1K tokens,使用费 0.<strong>1200</strong> USD/1K tokens,Davinci</li>\n</ul>\n\n<p>在 OpenAI 的 PlayGround 你可以试试:<a href=\"https://platform.openai.com/playground/p/default-chat\">https://platform.openai.com/playground/p/default-chat</a> 。</p>\n\n<p><img src=\"/img/src/2023/2023-01-24-openai-api.png\" alt=\"image\" /></p>\n\n<h2 id=\"三主要-api-介绍及代码示例\">三、主要 API 介绍及代码示例</h2>\n\n<p>安装 OpenAI 的 python 库,参考 <code class=\"language-plaintext highlighter-rouge\">https://anaconda.org/conda-forge/openai</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>conda <span class=\"nb\">install</span> <span class=\"nt\">-c</span> conda-forge openai\n</code></pre></div></div>\n\n<p>在 <code class=\"language-plaintext highlighter-rouge\">https://platform.openai.com/account/api-keys</code> 创建自己的 API。完成这两步后就可以编写代码尝试一下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"nb\">list</span><span class=\"p\">())</span>\n</code></pre></div></div>\n\n<p>会打印出 OpenAI 的各个 models 的一些信息、权限等等。</p>\n\n<h3 id=\"1text-completion-任务\">1、Text Completion 任务</h3>\n\n<p>下面这个例子会简单调用一下 completion,并打印出结果,用了一句需要你自己编写的 prompt:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">text_prompt</span> <span class=\"o\">=</span> <span class=\"s\">\"In a shocking turn of events, scientists have discovered that \"</span>\n<span class=\"n\">completion</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-002\"</span><span class=\"p\">,</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">text_prompt</span><span class=\"p\">,</span>\n <span class=\"n\">max_tokens</span><span class=\"o\">=</span><span class=\"mi\">100</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"n\">stop</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span>\n <span class=\"n\">temperature</span><span class=\"o\">=</span><span class=\"mf\">0.5</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">generated_text</span> <span class=\"o\">=</span> <span class=\"n\">completion</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">text</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">generated_text</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这里用到了最重要的 <code class=\"language-plaintext highlighter-rouge\">openai.Completion</code>,其 <code class=\"language-plaintext highlighter-rouge\">create</code> 函数的参数解释如下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">model</code>:之前 OpenAI 把它叫「engine」,后来给 deprecated 了,现在都是用 <code class=\"language-plaintext highlighter-rouge\">model</code>,所有的可用 models 可以通过 <code class=\"language-plaintext highlighter-rouge\">open.Model.list()</code> 来查看。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,就是输入数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">suffix</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,生成文本的结束符。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">max_tokens</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,生成文本的最大 tokens 数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:<code class=\"language-plaintext highlighter-rouge\">integer</code> 类型,表示你要产生几个不同的输出结果。比如设置 3 就会得到 3 个不同的结果,以便您可以从中选择最合适的一个。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">stop</code>:<code class=\"language-plaintext highlighter-rouge\">string</code> 类型,用于指定模型何时应该停止生成文本。当模型在生成的文本中遇到 stop 字符串时,它将停止生成文本。ChatGPT 推出后迭代过一版增加了「stop generating」就是用的这个参数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:<code class=\"language-plaintext highlighter-rouge\">number</code> 类型,这是 NLP 模型里常见的一个超参数。这个参数,来自于统计热力学的概念,温度越高表示系统的熵越高、混乱度越高、随机性越强,这里的 temperature 也是值越高输出结果的随机性也越高。这样如果 temperature 设置得很低,生成的结果可能更正确,但没有多少创造性和随机性。</li>\n</ul>\n\n<h3 id=\"2text-edit-任务\">2、Text Edit 任务</h3>\n\n<p>Completion 类任务,通俗点理解的话,完形填空、句子补齐、写作文、翻译 …… 都算 Completion,就是无中生有。而对于已经有的内容,做修改,就是 OpenAI 的 API 里的「Edit」类的任务了。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Edit</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">model</span><span class=\"o\">=</span><span class=\"s\">\"text-davinci-edit-001\"</span><span class=\"p\">,</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"The qick brown fox jumps over the layz dog.\"</span><span class=\"p\">,</span>\n <span class=\"n\">instruction</span><span class=\"o\">=</span><span class=\"s\">\"Fix the spelling mistakes\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>调用 <code class=\"language-plaintext highlighter-rouge\">openai.Edit.create</code>,用 <code class=\"language-plaintext highlighter-rouge\">text-davinci-edit-001</code> 模型,输入一句有拼写错误的英文「The qick brown fox jumps over the layz dog.」,并提供一句指令 instruction「Fix the spelling mistakes」。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">instruction</code>:要告诉模型如何修改,<strong>其实这句话就是新时代的「programming」了</strong>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">temperature</code>:默认是 0,对于纠正拼写类的任务,我们用默认 0 就可以了,不需要什么创造性和随机性。</li>\n</ul>\n\n<h3 id=\"3image-create-任务beta\">3、Image Create 任务(Beta)</h3>\n\n<p>截止 2023 年年初 1 月份,这个 API 还是 beta,我们看个例子:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这也是一个 OpenAI 官网的例子。大家可能看到这里,船长没有指定 model,但是可以想到一定用的是 DALL·E,因为它没有像 GPT-3 一样提供很多版本的选择,所以就不需要传参数了。这个程序就是生成一个 1024x1024 的图片。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">prompt</code>:就是输入的提示语,返回的数据里,会告诉你生成的图片的 URL.</li>\n <li><code class=\"language-plaintext highlighter-rouge\">n</code>:是图片结果数量,最多 10,默认 1.</li>\n</ul>\n\n<h3 id=\"4image-edit-任务\">4、Image Edit 任务</h3>\n\n<p>给定一个图片,OpenAI 也可以来修改指定区域:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">os</span>\n<span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">api_key</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">getenv</span><span class=\"p\">(</span><span class=\"s\">\"OPENAI_API_KEY\"</span><span class=\"p\">)</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Image</span><span class=\"p\">.</span><span class=\"n\">create_edit</span><span class=\"p\">(</span>\n <span class=\"n\">image</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"otter.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">mask</span><span class=\"o\">=</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"mask.png\"</span><span class=\"p\">,</span> <span class=\"s\">\"rb\"</span><span class=\"p\">),</span>\n <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"s\">\"A cute baby sea otter wearing a beret\"</span><span class=\"p\">,</span>\n <span class=\"n\">n</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">,</span>\n <span class=\"n\">size</span><span class=\"o\">=</span><span class=\"s\">\"1024x1024\"</span>\n<span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">image</code>:这里对输入图片有要求,必须是正方形的!另外不能超过 4MB,还得是 PNG。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">mask</code>:还可以提供掩码图片(叫什么比较合适,掩图?哈哈)。如果不提供的话,<code class=\"language-plaintext highlighter-rouge\">image</code> 里就必须有透明的部分(必须全透明,即 alpha = 0),那个透明部分就是被用来 Edit 的。如果有 <code class=\"language-plaintext highlighter-rouge\">mask</code> 则透明部分用来做「掩图」来改 <code class=\"language-plaintext highlighter-rouge\">image</code>。</li>\n <li>同样地,结果图片的 URL 会返回给你。</li>\n</ul>\n\n<h3 id=\"5审查moderation\">5、审查(Moderation)</h3>\n\n<p>Moderation 用来审查内容是否符合 OpenAI 的内容政策,快速使用的方式如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Moderation</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span>\n <span class=\"nb\">input</span><span class=\"o\">=</span><span class=\"s\">\"Sample text goes here\"</span>\n<span class=\"p\">)</span>\n<span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">[</span><span class=\"s\">\"results\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre></div></div>\n\n<p>API 官网给出我们如下的返回结果示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span>\n <span class=\"s\">\"id\"</span><span class=\"p\">:</span> <span class=\"s\">\"modr-XXXXX\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"model\"</span><span class=\"p\">:</span> <span class=\"s\">\"text-moderation-001\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"results\"</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n <span class=\"p\">{</span>\n <span class=\"s\">\"categories\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"n\">false</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"category_scores\"</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"s\">\"hate\"</span><span class=\"p\">:</span> <span class=\"mf\">0.18805529177188873</span><span class=\"p\">,</span>\n <span class=\"s\">\"hate/threatening\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0001250059431185946</span><span class=\"p\">,</span>\n <span class=\"s\">\"self-harm\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0003706029092427343</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0008735615410842001</span><span class=\"p\">,</span>\n <span class=\"s\">\"sexual/minors\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0007470346172340214</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence\"</span><span class=\"p\">:</span> <span class=\"mf\">0.0041268812492489815</span><span class=\"p\">,</span>\n <span class=\"s\">\"violence/graphic\"</span><span class=\"p\">:</span> <span class=\"mf\">0.00023186142789199948</span>\n <span class=\"p\">},</span>\n <span class=\"s\">\"flagged\"</span><span class=\"p\">:</span> <span class=\"n\">false</span>\n <span class=\"p\">}</span>\n <span class=\"p\">]</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n\n<p>输入参数很简单,关键看返回的输出结果。OpenAI 对于包含哪类不适内容,做了比较详尽的分类,比如对于色情内容,也分成了未成年色情和易引起性兴奋的内容。</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hate</code>:是否包含基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓表达、煽动或促进仇恨的内容,如果没有则是 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">hate/threatening</code>:是否包含仇恨内容还包括对目标群体的暴力或严重伤害,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,包含则值为 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">self-harm</code>:是否包含提倡、鼓励或描述自残行为(例如自杀、割伤和饮食失调)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual</code>:是否包含意在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sexual/minors</code>:是否包含包含 18 岁以下个人的色情内容,没有则 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence</code>:是否包含宣扬或美化暴力或颂扬他人的痛苦或屈辱的内容,没有为 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">violence/graphic</code>:是否包含以极端的画面细节描绘死亡、暴力或严重身体伤害的暴力内容,没有 <code class=\"language-plaintext highlighter-rouge\">false</code>,否则 <code class=\"language-plaintext highlighter-rouge\">true</code>。</li>\n</ul>\n\n<p>显然,对于使用 OpenAI 生成内容的场景下如果需要用到 Moderation,则是免费调用的。如果你不是对 OpenAI 的输入 & 生成场景,而是自己的其他内容想白嫖 Moderation API 是不可能的。但是我们也注意到,这里其实没有整治敏感的分类,因为 OpenAI 没有考虑具体的使用者所处的政体或政治环境,而且这些尺度是比较容易变化的,并且有一些可能并不是普适性的理念,因此某些国家的使用者要额外配套自己的内容审查能力。</p>\n\n<h2 id=\"四精调fine-tuning\">四、精调(Fine-tuning)</h2>\n\n<p><strong>Few-shot learning 是什么?</strong>:GPT-3 用了互联网上的海量文本数据训练,所以当你给少量示例(a promopt with just a few examples)时,GPT-3 会从「直觉上」知道你大概是想要解决什么任务,然后给出一些大概齐的反馈内容作为 completion,这通常就被叫做「few-shot learning」或者「few-shot prompting」。</p>\n\n<p>而如果你提供一些针对目标任务的训练数据,很可能可以实现没有 examples 也可以执行任务,也就是使用时连「few-shot learning」都免了。OpenAI 也提供了让用户自己 fine-tune 模型的接口,自己 fine-tune 的好处是:</p>\n\n<ul>\n <li><strong>高质量</strong>:这是显然的,比「设计提示(prompt design)」得到的结果质量更高。</li>\n <li><strong>相当于批量 prompt</strong>:可以比 prompt 给模型更多的 examples,比如用一个文件,里面包含大量用于 fine-tuning 的输入数据。</li>\n <li><strong>更省</strong>:可以更省 tokens,也就更省钱。</li>\n <li><strong>更快</strong>:更低的延迟的请求响应。</li>\n</ul>\n\n<p><strong>步骤和价格</strong>方面,Fine-tune 一共三步:上传用于 fine-tune 的数据、用数据 fine-tune 模型、使用属于你自己的 fine-tune 过的模型。从定价上我们看到 Fine-tune 后的模型使用费用基本翻了 4~6 倍,可以说相比基本模型的使用,是非常贵了。</p>\n\n<p>另外 OpenAI 也支持你对一个 fine-tune 过的模型继续 fine-tune,而不用从头开始。目前 davinci、curie、babbage、ada 都支持 fine-tuning。训练数据的格式也很简单,就是一组 prompt-completion 的 JSONL 文件,just like this:</p>\n\n<div class=\"language-json highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">{</span><span class=\"nl\">\"prompt\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<prompt text>\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"nl\">\"completion\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"<ideal generated text>\"</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"err\">...</span><span class=\"w\">\n</span></code></pre></div></div>\n\n<p>Fine-tune 的 example 与 few-shot learning 的最大区别:</p>\n\n<ul>\n <li>few-shot learning 要给出详尽的 instruction 来描述任务</li>\n <li>few-shot learning 的一个 prompt 是在使用时给出的,所以一个 prompt 大概率会带多个 examples(相对详细);而 fine-tune 的 example 都是一些简单直接的 prompt 以及直接对应的 completion。</li>\n</ul>\n\n<p>OpenAI 建议 fine-tune 的 examples 数量至少几百(a couple hundred)。另外 fine-tune 也符合 scaling law,基本上 fine-tune 的数据集成倍上翻的话,效果是线性增长的。</p>\n\n<h3 id=\"1创建一个-fine-tune-模型\">1、创建一个 fine-tune 模型</h3>\n\n<p>CLI 下运行如下命令,其中 <code class=\"language-plaintext highlighter-rouge\"><TRAIN_FILE_ID_OR_PATH></code> 是你的训练数据文件,<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code> 是你要用的模型,具体的参数可以用 <code class=\"language-plaintext highlighter-rouge\">ada</code>、<code class=\"language-plaintext highlighter-rouge\">babbage</code>、<code class=\"language-plaintext highlighter-rouge\">curie</code> 和 <code class=\"language-plaintext highlighter-rouge\">davinci</code>。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.create <span class=\"nt\">-t</span> <TRAIN_FILE_ID_OR_PATH> <span class=\"nt\">-m</span> <BASE_MODEL>\n</code></pre></div></div>\n\n<p>这句命令让 OpenAI 不仅基于 base model 创建了一个模型,而且开始运行训练任务。训练任务可能会花费几分钟、几小时甚至根据,取决于你的训练集和模型选择。训练任务可能会被 OpenAI 排队,不一定马上开始运行。如果过程中被打断了,可以如下继续:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.follow <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>保存一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.get <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<p>取消一个 fine-tune job 的命令如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>openai api fine_tunes.cancel <span class=\"nt\">-i</span> <YOUR_FINE_TUNE_JOB_ID>\n</code></pre></div></div>\n\n<h3 id=\"2使用-fine-tuned-模型\">2、使用 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Completion</span><span class=\"p\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"o\">=</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"o\">=</span><span class=\"n\">YOUR_PROMPT</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"3删掉一个-fine-tuned-模型\">3、删掉一个 fine-tuned 模型</h3>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">openai</span>\n<span class=\"n\">openai</span><span class=\"p\">.</span><span class=\"n\">Model</span><span class=\"p\">.</span><span class=\"n\">delete</span><span class=\"p\">(</span><span class=\"n\">FINE_TUNED_MODEL</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"4一个-fine-tuned-模型之上继续-fine-tune\">4、一个 fine-tuned 模型之上继续 fine-tune</h3>\n\n<p>如果你微调了一个模型,现在又有为的训练数据想要合并进来,可以基于已 fine-tuned 模型继续微调,无需从头再全部训练一遍。唯一要做的,就是在创建新的 fine-tune job 时传入已 fine-tune 的模型名称,替代<code class=\"language-plaintext highlighter-rouge\"><BASE_MODEL></code>(例如 <code class=\"language-plaintext highlighter-rouge\">-m curie:ft-<org>-<date></code>),不必更改其他训练参数。</p>\n\n<p>有一个要注意的,如果新增的训练数据比以前的训练数据规模小得多,那最好把 <code class=\"language-plaintext highlighter-rouge\">learning_rate_multiplier</code> 减少 2 到 4 倍,否则很可能跳过了最优解。</p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://openai.com/api/</li>\n <li>https://developer.aliyun.com/article/933516</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</title>\n \t<meta name=\"description\" content=\"基于 RNN 的 Encoder-Decoder 模型存在无法处理过长文本、并行性差的两大痛点。2015 年 Bahdanau 等人在其论文中提出 Attention 机制,再到 2017 年 Transformer 模型的论文《Attention is All You Need》横空出世,其并行速度极快,而且每两个词之间的词间距都是 1。此后 NLP 领域 Transformer 彻底成为主流。如果你已经了解 Encoder-Decoder 模型,本文将基于此带你深入浅出的搞清楚 Attention、Transformer。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>人工智能 LLM 革命前夜:一文读懂横扫自然语言处理的 Transformer 模型</h2>\t\t\n\t<time datetime=\"2023-01-22T09:13:09+00:00\" class=\"by-line\">22 Jan 2023, 香港 | 麦克船长 | 总计 78254 字</time>\n\t<div class=\"content\">\n\t\t<h2 id=\"前言\">前言</h2>\n\n<p>本文试图从技术角度搞清楚一个问题:<strong>过去一年 AIGC 爆火、过去五年 NLP(自然语言处理)领域突飞猛进的缘起是什么?</strong></p>\n\n<p>这个问题被解答后,将还有两个问题,但暂时本文没有作答:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>利用春节时间,写了这么一篇数万字的长文笔记,希望共同爱好的朋友能读完多多指正。我是船涨,网名一直用「麦克船长」,中科大计算机本科毕业后先是做的 RTC 技术、分布式系统等等,干过 Full Stack,后来创业在产品、运营、营销、供应链上折腾了些年后来到阿里,在淘系做过产品、运营。</p>\n\n<h4 id=\"1我来阿里之后第一个新增爱好是变形金刚模型第二个新增爱好是变形金刚模型\">1、我来阿里之后第一个新增爱好是「变形金刚模型」,第二个新增爱好是「变形金刚模型」</h4>\n\n<p>写了个这么冷的梗,其实想说的是,前者指的是著名 IP「变形金刚」相关的手办玩具模型,后者指的是这个引领革命的人工智能语言模型 Transformer。这两个爱好,都与目前从事的电商工作本职没有表面上的直接联系,权当爱好了。</p>\n\n<p>2022 年「生成式 AI」应用取得了突飞猛进的发展,作为一个「古典互联网」从业者,深切地感到这一次 AI 技术可能会带来的颠覆式变革,这让我兴奋又焦虑。2022 年上半年,我从天天特卖业务负责人到大聚划算运营中心负责人,在去年相当长一段时间里在关注直播带货在营销平台的模式命题,一直在思考一个问题:直播电商的高效(更适合的商品演绎方式 + 私域权益 + 冲动购买等」vs. 直播电商的低效(直播分发无人货匹配 + 直播间内千人一面 + 货品状态未知 + 主播不可控等),能否推动一个保留直播的高效,同时解决直播的低效的模式呢?</p>\n\n<p>这里面有大量的内容值得探讨,不过这不是船涨该系列文章的初衷,但这是我为什么开始非常关注 AI 的引子。直播电商的数字人技术基础,有动作捕捉、面部表情模拟、视觉渲染、直播话术生成、语音合成等等。依据第一性原理抽丝剥茧后,我发现尽管动作捕捉、视觉渲染等等很多技术仍有很大挑战,但是从商业视角看真正最影响用户心智的,是直播话术生成和演绎,除了头部主播,绝大多数直播带货在这方面都做的很糟糕,那么这里面就有巨大的「机器学习」生成内容超越非头部的大多数从业者的市场空间,而这完全依赖自然语言处理(NLP)。</p>\n\n<p>这个问题就属于「生成式 AI」的范畴了,国外科技圈叫它「Gen-AI」,即 Generative AI,中国科技圈都叫它「AIGC」,即 AI Generated Content,与 UGC、PGC 相对应。Gen-AI 的叫法更关注主体,具体地说是「生成式 AI 模型」,它是个「内容引擎」。而中国的叫法更关注「内容应用」。</p>\n\n<p>讲到 AIGC 这里,大家熟悉的 ChatGPT 就在 2022 年年底登场了。也是因为 ChatGPT 的破圈,带来了 AIGC 在国内科技圈的关注度暴涨。我从去年年中开始关注「文生图,text2image」领域的明星 Stable Diffusion 开源,进而关注到了 text2image 应用的爆发,包括 Disco Diffusion、MidJourney、DALL·E 2 等等,这些都源于 CV(计算机视觉)领域因为 Diffusion 模型发展带来的技术突破。</p>\n\n<p>AI 生成图片确实非常惊人。我酷爱变形金刚模玩,进而对机甲类都非常喜欢,所以随手生成了几张图,这里贴一下大家看看,分钟级的创作速度。(注意:当下 AI 生成图片主要是基于 Diffusion 的应用发展,AI 生成文本的核心驱动才是 Transformer 模型,此处只是展示)</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>但是从第一性原理角度讲,生成图片的应用广度,远远小于生成文本。文本内容的本质是语言文字的理解与生成,人类历史有 600 万年,但是人类文明历史大概就 6000 年,文明的大发展出现在近 2000 多年的原因,主要来自 3500 多年前人类发明了文字。所以 AI 生成文本,意味着 AI 可以用人类熟悉的方式(语言文字)与人类高效协作,这必将引爆生产力革命。而这必将深入影响电商、内容、游戏、云计算、企业服务等众多领域。</p>\n\n<h4 id=\"2掌握技术基础是当下读懂-ai-脉搏的基本功而这个脉搏将带动各行各业\">2、掌握技术基础,是当下读懂 AI 脉搏的基本功,而这个脉搏将带动各行各业</h4>\n\n<p>一旦深入关注 AI、关注 NLP 领域,你就会发现当下仍然处于一个技术发展突破的阶段,不关注技术的情况下来聊 AI、聊 NLP、聊 AIGC,那就只能是一个「爱好者」,而无法深入与这个行业内的弄潮儿对话,更不要提参与其中了。所以这个春节,船涨回归了当年做技术时的初心,翻了一些材料,学习了 NLP 语言模型的关键技术,在此作为技术学习笔记,与大家分享。尽管担心班门弄斧,但是本着费曼老师提倡的输出学习法,我把自己学习梳理的内容抛出来,除了会更帮助到我自己,也能结交一些对此同样在关注的同学们,欢迎感兴趣的同学加我的微信(微信号 sinosuperman)在业余时间和我交流。</p>\n\n<p>本文将包括这几部分:</p>\n\n<ul>\n <li><strong>第一章,主要介绍 Transformer 出现之前的几个主流语言模型,包括 N 元文法(n-gram)、多层感知器(MLP)、卷积神经网络(CNN)、循环神经网络(RNN)。其中 CNN 主要应用领域在计算机视觉,因此没有更详细展开。其他模型也未面面俱到,主要考虑还是一个领域学习者的角度来了解和应用,而非研究。</strong></li>\n <li><strong>第二章,是本文的核心,先介绍了注意力机制(Attention Mechanism),然后基于第一章对此前几大语言模型了解后,我们能更好地理解 Transformer 为什么会带来革命性的影响。</strong></li>\n <li><strong>第三章,是一个 Transformer 的实现版本,基于 Tensorflow。</strong></li>\n</ul>\n\n<p>阅读本文,先对你过往的基础知识做了一些假设,如果你暂未了解,可能在阅读时遇到以下内容做一些简单地查询即可:</p>\n\n<ul>\n <li>Word Presentation:自然语言处理中的词表示法,主要涉及 embedding。</li>\n <li>张量:需要一点基础,比如了解张量的形状、升降维度等。但不会涉及到复杂问题,对一阶张量(向量)、二阶张量(矩阵)的简单运算有数学基础即可。对三阶张量,大概能想象出其空间含义即可。语言模型里理解词之间的距离,是有其空间几何意义的。</li>\n <li>技术框架:PyTorch 或 TensorFlow 框架。由于时间和篇幅关系,春节期间梳理这些时,对于框架基础,我主要是 Google 现用现查,询问 ChatGPT 以及在微信读书里直接搜索全文。</li>\n</ul>\n\n<p>作为技术笔记难免有纰漏或理解错误,欢迎指正。文中自绘图片用的是 Graphviz,公式生成用的是 KaTeX,贴到 ATA 后难免有一些没有兼容的部分(发现的已做了 fix),望见谅。</p>\n\n<h2 id=\"第一章--2017-年之前的几个关键-nlp-语言模型\">第一章 · 2017 年之前的几个关键 NLP 语言模型</h2>\n\n<p>NLP 的技术基础方面,我认为主要是这两部分:词表示法(Word Presentation)、语言模型(Language Model)。对于词表示法,这里不做详细介绍,基本的思路就是把词表示为向量(一维张量),最基本的 One-Hot、Word2Vec、GloVe、fastText 等。这部分的技术演进也在不断前进,比如本文将要重点介绍的 Transformer 模型里,用到的词表示法是「引入上下文感知的词向量」。</p>\n\n<p>语言模型从早期的 N 元文法(N-Gram,本文要介绍的),到神经网络被提出后最早期的感知器(Perceptron),再到后来席卷计算机视觉(CV)领域的卷积神经网络(CNN),然后出现考虑序列特征的循环神经网络(RNN,包括 Encoder-Decoder 模型),直到 2017 年横空出世的 Transformer,大概分这五个主要阶段。因为本文的重点是 Transformer,所以前面四个模型我会快速概览一下,然后介绍下最朴素的注意力(Attention)机制,基于此再详细介绍下 Transformer,并对一个完整的、精炼实现的代码实例进行精讲。</p>\n\n<h3 id=\"第-1-节--n-元文法语言模型\">第 1 节 · N 元文法语言模型</h3>\n\n<h4 id=\"11马尔科夫假设markov-assumption与-n-元文法语言模型n-gram-language-model\">1.1、马尔科夫假设(Markov Assumption)与 N 元文法语言模型(N-gram Language Model)</h4>\n\n<p>下一个词出现的概率只依赖于它前面 n-1 个词,这种假设被称为「马尔科夫假设(Markov Assumption」。N 元文法,也称为 N-1 阶马尔科夫链。</p>\n\n<ul>\n <li>一元文法(1-gram),unigram,零阶马尔科夫链,不依赖前面任何词;</li>\n <li>二元文法(2-gram),bigram,一阶马尔科夫链,只依赖于前 1 个词;</li>\n <li>三元文法(3-gram),trigram,二阶马尔科夫链,只依赖于前 2 个词;</li>\n <li>……</li>\n</ul>\n\n<p>通过前 t-1 个词预测时刻 t 出现某词的概率,用最大似然估计:</p>\n\n\\[P(w_t | w_1,w_2...w_{t-1}) = \\frac{C(w_1,w_2,...w_t)}{C(w_1,w_2,...w_{t-1})}\\]\n\n<p>进一步地,一组词(也就是一个句子)出现的概率就是:</p>\n\n\\[P(w_1,w_2,...w_t) = P(w_t | w_1,w_2,...w_{t-1}) \\cdot P(w_{t-1} | w_1,w_2,...w_{t-2}) \\cdot ... \\cdot P(w_1)\n\t\t\t = \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{1:i-1})\\]\n\n<p>为了解决句头、尾逇概率计算问题,我们再引入两个标记 <code class=\"language-plaintext highlighter-rouge\"><BOS></code> 和 <code class=\"language-plaintext highlighter-rouge\"><EOS></code> 分别表示 beginning of sentence 和 end of sentence,所以 \\(w_0 =\\) <BOS>、 \\(w_{length + 1} =\\) <EOS>,其中 length 是词的数量。</p>\n\n<p>具体地,比如对于 bigram,该模型表示如下:</p>\n\n\\[\\begin{aligned}\nP(w_1,w_2,...w_t) &= \\displaystyle\\prod_{i=1}^{t-1}P(w_i | w_{i-1}) \\\\\nP(w_t | w_{t-1}) &= \\frac{C(w_{t-1}, w_t)}{C(w_{t-1})}\n\\end{aligned}\\]\n\n<ul>\n <li>如果有词出现次数为了 0,这一串乘出来就是 0 了,咋办?</li>\n <li>因为基于马尔科夫假设,所以 N 固定窗口取值,对长距离词依赖的情况会表现很差。</li>\n <li>如果把 N 值取很大来解决长距离词依赖,则会导致严重的数据稀疏(零频太多了),参数规模也会急速爆炸(高维张量计算)。</li>\n</ul>\n\n<p>上面的第一个问题,我们引入平滑 / 回退 / 差值等方法来解决,而后面两个问题则是在神经网络模型出现后才更好解决的。</p>\n\n<h4 id=\"12平滑smoothing-折扣discounting\">1.2、平滑(Smoothing)/ 折扣(Discounting)</h4>\n\n<p>虽然限定了窗口 n 大小降低了词概率为 0 的可能性,但当 n-gram 的 n 比较大的时候会有的未登录词问题(Out Of Vocabulary,OOV)。另一方面,训练数据很可能也不是 100% 完备覆盖实际中可能遇到的词的。所以为了避免 0 概率出现,就有了让零平滑过渡为非零的补丁式技术出现。</p>\n\n<p>最简单的平滑技术,就是折扣法(Discounting)。这是一个非常容易想到的办法,就是把整体 100% 的概率腾出一小部分来,给这些零频词(也常把低频词一起考虑)。常见的平滑方法有:加 1 平滑、加 K 平滑、Good-Turing 平滑、Katz 平滑等。</p>\n\n<h5 id=\"121加-1-平滑--拉普拉斯平滑add-one-discounting--laplace-smoothing\">1.2.1、加 1 平滑 / 拉普拉斯平滑(Add-One Discounting / Laplace Smoothing)</h5>\n\n<p>加 1 平滑,就是直接将所有词汇的出现次数都 +1,不止针对零频词、低频词。如果继续拿 bigram 举例来说,模型就会变成:</p>\n\n\\[P(w_i | w_{i-1}) = \\frac{C_(w_{i-1},w_i) + 1}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + 1)} = \\frac{C(w_{i-1}, w_i) + 1}{C(w_{i-1}) + |\\mathbb{V}|}\\]\n\n<table>\n <tbody>\n <tr>\n <td>其中 \\(N\\) 表示所有词的词频之和, $$</td>\n <td>\\mathbb{V}</td>\n <td>$$ 表示词汇表的大小。</td>\n </tr>\n </tbody>\n</table>\n\n<p>如果当词汇表中的词,很多出现次数都很小,这样对每个词的词频都 +1,结果的偏差影响其实挺大的。换句话说,+1 对于低频词很多的场景,加的太多了,应该加一个更小的数( 1 < δ < 1)。所以有了下面的「δ 平滑」技术。</p>\n\n<h5 id=\"122加-k-平滑--δ-平滑add-k-discounting--delta-smoothing\">1.2.2、加 K 平滑 / δ 平滑(Add-K Discounting / Delta Smoothing)</h5>\n\n<p>把 +1 换成 δ,我们看下上面 bigram 模型应该变成上面样子:</p>\n\n\\[P(w_i | w{i-1}) = \\frac{C_(w_{i-1},w_i) + \\delta}{\\displaystyle\\sum_{j=1}^n(C_(w_{i-1},w_j) + \\delta)} = \\frac{C(w_{i-1}, w_i) + \\delta}{C(w_{i-1}) + \\delta|\\mathbb{V}|}\\]\n\n<p>δ 是一个超参数,确定它的值需要用到困惑度(Perplexity,一般用缩写 PPL)。另外,有些文章里也会把这个方法叫做「加 K 平滑,Add-K Smoothing」。</p>\n\n<h5 id=\"123困惑度perplexity\">1.2.3、困惑度(Perplexity)</h5>\n\n<p>对于指定的测试集,困惑度定义为测试集中每一个词概率的几何平均数的倒数,公式如下:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = \\frac{1}{\\sqrt[n]{P(w_1,w_2...w_n)}}\\]\n\n<p>把 \\(P(w_1,w_2,...w_t) = \\displaystyle\\prod_{i=1}^{t-1}P(w_i\\text{\\textbar}w_{i-1})\\) 带入上述公式,就得到了 PPL 的计算公式:</p>\n\n\\[\\operatorname{PPL}(\\mathbb{D}_{test}) = (\\displaystyle\\prod_{i=1}^nP(w_i|w_{1:i-1}))^{-\\frac{1}{n}}\\]\n\n<h4 id=\"13回退back-off\">1.3、回退(Back-off)</h4>\n\n<p>在多元文法模型中,比如以 3-gram 为例,如果出现某些三元语法概率为零,则不使用零来表示概率,而回退到 2-gram,如下。</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) =\n\\begin{cases}\nP(w_i|w_{i-2}w_{i-1}) & C(w_{i-2}w_{i-1}w_i) > 0 \\\\\nP(w_i|w_{i-1}) & C(w_{i-2}w_{i-1}w_i) = 0 \\enspace and \\enspace C(w_{i-1}w_i) > 0\n\\end{cases}\\]\n\n<h4 id=\"14差值interpolation\">1.4、差值(Interpolation)</h4>\n\n<p>N 元文法模型如果用回退法,则只考虑了 n-gram 概率为 0 时回退为 n-1 gram,那么自然要问:n-gram 不为零时,是不是也可以按一定权重来考虑 n-1 gram?于是有了插值法。以 3-gram 为例,把 2-gram、1-gram 都考虑进来:</p>\n\n\\[P(w_i|w_{i-2}w_{i-1}) = \\lambda_1 P(w_i|w_{i-2}w_{i-1}) + \\lambda_2 P(w_i|w_{i-1}) + \\lambda_3 P(w_i)\\]\n\n<h3 id=\"第-2-节--感知器perceptron\">第 2 节 · 感知器(Perceptron)</h3>\n\n<p>N 元文法模型的显著问题,在「马尔科夫假设与 N 元文法语言模型」小节已经提到了。这些问题基本在神经网络模型中被解决,而要了解神经网络模型,就要从感知器(Perceptron)开始。1957 年感知机模型被提出,1959 年多层感知机(MLP)模型被提出。MLP 有时候也被称为 ANN,即 Artificial Neural Network,接下来我们来深入浅出地了解一下,并有一些动手的练习。</p>\n\n<h4 id=\"21感知器perceptron解决二元分类任务的前馈神经网络\">2.1、感知器(Perceptron):解决二元分类任务的前馈神经网络</h4>\n\n<p>\\(x\\) 是一个输入向量,\\(\\omega\\) 是一个权重向量(对输入向量里的而每个值分配一个权重值所组成的向量)。举一个具体任务例子,比如如果这两个向量的内积超过某个值,则判断为 1,否则为 0,这其实就是一个分类任务。那么这个最终输出值可以如下表示:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x \\geq 0) \\\\ 0 & (\\omega \\cdot x \\lt 0) \\end{cases}\\]\n\n<p>这就是一个典型的感知器(Perceptron),一般用来解决分类问题。还可以再增加一个偏差项(bias),如下:</p>\n\n\\[y = \\begin{cases} 1 & (\\omega \\cdot x + b \\geq 0) \\\\ 0 & (\\omega \\cdot x + b \\lt 0) \\end{cases}\\]\n\n<p>感知器其实就是一个前馈神经网络,由输入层、输出层组成,没有隐藏层。而且输出是一个二元函数,用于解决二元分类问题。</p>\n\n<h4 id=\"22线性回归linear-regression从离散值的感知器解决类问题到连续值的线性回归解决回归问题\">2.2、线性回归(Linear Regression):从离散值的感知器(解决类问题),到连续值的线性回归(解决回归问题)</h4>\n\n<p>一般来说,我们认为感知器的输出结果,是离散值。一般来说,我们认为离散值作为输出解决的问题,是分类问题;相应地,连续值解决的问题是回归(Regression)。比如对于上面的感知器,如果我们直接将 \\(\\omega \\cdot x + b\\) 作为输出值,则就变成了一个线性回归问题的模型了。</p>\n\n<p>下面我们用 PyTorch 来实现一个线性回归的代码示例,首先我们要了解在 PyTorch 库里有一个非常常用的函数:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">in_features</span><span class=\"p\">,</span> <span class=\"n\">out_features</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这个函数在创建时会自动初始化权值和偏置,并且可以通过调用它的 <code class=\"language-plaintext highlighter-rouge\">forward</code> 函数来计算输入数据的线性变换。具体来说,当输入为 <code class=\"language-plaintext highlighter-rouge\">x</code> 时,<code class=\"language-plaintext highlighter-rouge\">forward</code> 函数会计算 \\(y = \\omega \\cdot x + b\\),其中 \\(W\\) 和 \\(b\\) 分别是 <code class=\"language-plaintext highlighter-rouge\">nn.Linear</code> 图层的权值和偏置。</p>\n\n<p>我们来一个完整的代码示例:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 定义模型\n</span><span class=\"k\">class</span> <span class=\"nc\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">LinearRegression</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">linear</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 初始化模型\n</span><span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">LinearRegression</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">output_size</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">MSELoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">SGD</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"mf\">0.01</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 创建输入特征 X 和标签 y\n</span><span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">3</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">]])</span>\n<span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">2</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"p\">):</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 创建测试数据 X_test 和标签 y_test\n</span><span class=\"n\">X_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">5</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">6</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">7</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">8</span><span class=\"p\">]])</span>\n<span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">Tensor</span><span class=\"p\">([[</span><span class=\"mi\">10</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">12</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">14</span><span class=\"p\">],</span> <span class=\"p\">[</span><span class=\"mi\">16</span><span class=\"p\">]])</span>\n\n<span class=\"c1\"># 测试模型\n</span><span class=\"k\">with</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">no_grad</span><span class=\"p\">():</span>\n <span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">predictions</span><span class=\"p\">,</span> <span class=\"n\">y_test</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Test loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">:.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n</code></pre></div></div>\n\n<p>上述代码,一开始先创建一个 <code class=\"language-plaintext highlighter-rouge\">LinearRegression</code> 线性回归模型的类,其中有一个 <code class=\"language-plaintext highlighter-rouge\">forward</code> 前向传播函数,调用时其实就是计算一下输出值 <code class=\"language-plaintext highlighter-rouge\">y</code>。</p>\n\n<p>主程序,一开始创建一个线性回归模型实例,然后定义一个用于评价模型效果的损失函数评价器,和用随机梯度下降(Stochastic Gradient Descent)作为优化器。</p>\n\n<p>然后创建一个输入特征张量,和标签张量。用这组特征和标签进行训练,训练的过程就是根据 <code class=\"language-plaintext highlighter-rouge\">X</code> 计算与测试 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 向量,再把它和 <code class=\"language-plaintext highlighter-rouge\">y</code> 一起给评价器算出损失 <code class=\"language-plaintext highlighter-rouge\">loss</code>,然后进行反向传播。注意反向传播的三行代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n<span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<p>如此训练 100 次(每一次都会黑盒化地更新模型的参数,一个 <code class=\"language-plaintext highlighter-rouge\">epoch</code> 就是一次训练过程,有时也称为 <code class=\"language-plaintext highlighter-rouge\">iteration</code> 或者 <code class=\"language-plaintext highlighter-rouge\">step</code>,不断根据 <code class=\"language-plaintext highlighter-rouge\">loss</code> 训练优化模型参数。</p>\n\n<p>然后我们创建了一组测试特征值张量 <code class=\"language-plaintext highlighter-rouge\">X_test</code>,和测试标签张量 <code class=\"language-plaintext highlighter-rouge\">y_test</code>,然后用它们测试模型性能,把测试特征得到的 <code class=\"language-plaintext highlighter-rouge\">predictions</code> 与 <code class=\"language-plaintext highlighter-rouge\">y_test</code> 共同传给评价器,得到 <code class=\"language-plaintext highlighter-rouge\">loss</code>。在这个例子中我们会得到如下结果:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">Test</span> <span class=\"n\">loss</span><span class=\"p\">:</span> <span class=\"mf\">0.0034</span>\n</code></pre></div></div>\n\n<h4 id=\"23逻辑回归logistic-regression没有值域约束的线性回归到限定在一个范围内的逻辑回归常用于分类问题\">2.3、逻辑回归(Logistic Regression):没有值域约束的线性回归,到限定在一个范围内的逻辑回归(常用于分类问题)</h4>\n\n<p>可以看到线性回归问题,输出值是没有范围限定的。如果限定(limit)在特定的 \\((0, L)\\) 范围内,则就叫做逻辑回归了。那么如何将一个线性回归变成逻辑回归呢?一般通过如下公式变换:</p>\n\n\\[y = \\frac{L}{1 + e^{-k(z-z_0)}}\\]\n\n<p>这样原来的 \\(z \\in (-\\infty, +\\infty)\\) 就被变换成了 \\(y \\in (0, L)\\) 了。</p>\n\n<ul>\n <li><strong>激活函数</strong>:这种把输出值限定在一个目标范围内的函数,被叫做 <strong>激活函数(Activation Function)</strong>。</li>\n <li><strong>函数的陡峭程度</strong> 由 \\(k\\) 控制,越大越陡。</li>\n <li>当 \\(z = z_0\\) 时, \\(y = \\frac{L}{2}\\) 。</li>\n</ul>\n\n<p>下面给出一个基于 Python 的 scikit-learn 库的示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">sklearn.linear_model</span> <span class=\"kn\">import</span> <span class=\"n\">LogisticRegression</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.datasets</span> <span class=\"kn\">import</span> <span class=\"n\">load_iris</span>\n<span class=\"kn\">from</span> <span class=\"nn\">sklearn.model_selection</span> <span class=\"kn\">import</span> <span class=\"n\">train_test_split</span>\n\n<span class=\"c1\"># 这是 scikit-learn 库里的一个简单的数据集\n</span><span class=\"n\">iris</span> <span class=\"o\">=</span> <span class=\"n\">load_iris</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 把 iris 数据集拆分成训练集和测试集两部分\n</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">,</span> <span class=\"n\">y_test</span> <span class=\"o\">=</span> <span class=\"n\">train_test_split</span><span class=\"p\">(</span><span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">,</span> <span class=\"n\">iris</span><span class=\"p\">.</span><span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">test_size</span><span class=\"o\">=</span><span class=\"mf\">0.25</span><span class=\"p\">,</span> <span class=\"n\">random_state</span><span class=\"o\">=</span><span class=\"mi\">42</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用 scikit-learn 库创建一个逻辑回归模型的实例\n</span><span class=\"n\">lr</span> <span class=\"o\">=</span> <span class=\"n\">LogisticRegression</span><span class=\"p\">()</span>\n\n<span class=\"c1\"># 用上边 split 出来的训练集数据,训练 lr 模型实例\n</span><span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">fit</span><span class=\"p\">(</span><span class=\"n\">X_train</span><span class=\"p\">,</span> <span class=\"n\">y_train</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用训练过的模型,拿测试集的输入数据做测试\n</span><span class=\"n\">predictions</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">predict</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 用测试集的数据验证精确性\n</span><span class=\"n\">accuracy</span> <span class=\"o\">=</span> <span class=\"n\">lr</span><span class=\"p\">.</span><span class=\"n\">score</span><span class=\"p\">(</span><span class=\"n\">X_test</span><span class=\"p\">,</span> <span class=\"n\">predictions</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">accuracy</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"24sigmoid-回归sigmoid-regression归一化的逻辑回归一般用于二元分类任务\">2.4、Sigmoid 回归(Sigmoid Regression):归一化的逻辑回归,一般用于二元分类任务</h4>\n\n<p>当 \\(L = 1, k = 1, z_0 = 0\\) ,此时的激活函数就是 <strong>Sigmoid</strong> 函数,也常表示为 \\(\\sigma\\) 函数,如下:</p>\n\n\\[y = \\frac{1}{1 + e^{-z}}\\]\n\n<p>Sigmoid 回归的值域,恰好在 (0, 1) 之间,所以常备作为用来归一化的激活函数。而一个线性回归模型,再用 sigmoid 函数归一化,这种也常被称为「Sigmoid 回归」。Sigmoid 这个单词的意思也就是 S 形,我们可以看下它的函数图像如下:</p>\n\n<p><img src=\"/img/src/2022-12-19-language-model-2.png\" alt=\"image\" width=\"490\" /></p>\n\n<p>因为归一化,所以也可以把输出值理解为一个概率。比如我们面对一个二元分类问题,那么输出结果就对应属于这个类别的概率。</p>\n\n<p>这样一个 sigmoid 模型可以表示为:</p>\n\n\\[y = Sigmoid(W \\cdot x + b)\\]\n\n<p>另外 sigmoid 函数的导数(即梯度)是很好算的: \\(y' = y \\cdot (1-y)\\) 。这非常方便用于「梯度下降算法」根据 loss 对模型参数进行优化。Sigmoid 回归,一般用于二元分类任务。那么对于超过二元的情况怎么办呢?这就引出了下面的 Softmax 回归。</p>\n\n<h4 id=\"25softmax-回归softmax-regression从解决二元任务的-sigmoid到解决多元分类任务的-softmax\">2.5、Softmax 回归(Softmax Regression):从解决二元任务的 sigmoid,到解决多元分类任务的 Softmax</h4>\n\n<p>相对逻辑回归,Softmax 也称为多项逻辑回归。上面说 Sigmoid 一般用于解决二元分类问题,那么多元问题就要用 Softmax 回归了。我们来拿一个具体问题来解释,比如问题是对于任意输入的一个电商商品的图片,来判断这个图片所代表的的商品,属于哪个商品类目。假设我们一共有 100 个类目。那么一个图片比如说其所有像素值作为输入特征值,输出就是一个 100 维的向量 ** \\(z\\) **,输出向量中的每个值 \\(z_i\\) 表示属于相对应类目的概率 \\(y_i\\) :</p>\n\n\\[y_i = Softmax(z)_i = \\frac{e^{z_i}}{e^{z_1} + e^{z_2} + ... + e^{z_100}}\\]\n\n<p>那么最后得到的 \\(y\\) 向量中的每一项就对应这个输入 \\(z\\) 属于这 100 个类目的各自概率了。所以如果回归到一般问题,这个 Softmax 回归的模型就如下:</p>\n\n\\[y = Softmax(W \\cdot x + b)\\]\n\n<p>对于上面电商商品图片的例子,假设每个图片的尺寸是 512x512,这个模型展开式如下:</p>\n\n\\[\\begin{bmatrix} y_1 \\\\ y_2 \\\\ ... \\\\ y_{100} \\end{bmatrix} = Softmax(\\begin{bmatrix} w_{1,1}, & w_{1,2}, & ... & w_{1, 512} \\\\ w_{2,1}, & w_{2,2}, & ... & w_{2, 512} \\\\ ... & ... & ... & ... \\\\ w_{100,1}, & w_{100,2}, & ... & w_{100, 512} \\end{bmatrix} \\cdot \\begin{bmatrix} x_1 \\\\ x_2 \\\\ ... \\\\ x_{512} \\end{bmatrix} + \\begin{bmatrix} b_1 \\\\ b_2 \\\\ ... \\\\ b_{512} \\end{bmatrix})\\]\n\n<p>这个对输入向量 \\(x\\) 执行 \\(w \\cdot x + b\\) 运算,一般也常称为「线性映射/线性变化」。</p>\n\n<h4 id=\"26多层感知器multi-layer-perceptron\">2.6、多层感知器(Multi-Layer Perceptron)</h4>\n\n<p>上面我们遇到的所有任务,都是用线性模型(Linear Models)解决的。有时候问题复杂起来,我们就要引入非线性模型了。</p>\n\n<p>这里我们要介绍一个新的激活函数 —— ReLU(Rectified Linear Unit)—— 一个非线性激活函数,其定义如下:</p>\n\n\\[ReLU(z) = max(0, z)\\]\n\n<p>比如对于 MNIST 数据集的手写数字分类问题,就是一个典型的非线性的分类任务,下面给出一个示例代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torchvision.transforms</span> <span class=\"k\">as</span> <span class=\"n\">transforms</span>\n\n<span class=\"c1\"># 定义多层感知器模型\n</span><span class=\"k\">class</span> <span class=\"nc\">MLP</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">(</span><span class=\"n\">MLP</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">).</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">):</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc1</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"n\">out</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">fc2</span><span class=\"p\">(</span><span class=\"n\">out</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">out</span>\n\n<span class=\"c1\"># 超参数\n</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"mi\">784</span>\n<span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"mi\">500</span>\n<span class=\"n\">num_classes</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n<span class=\"n\">num_epochs</span> <span class=\"o\">=</span> <span class=\"mi\">5</span>\n<span class=\"n\">batch_size</span> <span class=\"o\">=</span> <span class=\"mi\">100</span>\n<span class=\"n\">learning_rate</span> <span class=\"o\">=</span> <span class=\"mf\">0.001</span>\n\n<span class=\"c1\"># 加载 MNIST 数据集\n</span><span class=\"n\">train_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">(),</span>\n <span class=\"n\">download</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_dataset</span> <span class=\"o\">=</span> <span class=\"n\">datasets</span><span class=\"p\">.</span><span class=\"n\">MNIST</span><span class=\"p\">(</span><span class=\"n\">root</span><span class=\"o\">=</span><span class=\"s\">'../../data'</span><span class=\"p\">,</span>\n <span class=\"n\">train</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">transform</span><span class=\"o\">=</span><span class=\"n\">transforms</span><span class=\"p\">.</span><span class=\"n\">ToTensor</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># 数据加载器\n</span><span class=\"n\">train_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">train_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n<span class=\"n\">test_loader</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">utils</span><span class=\"p\">.</span><span class=\"n\">data</span><span class=\"p\">.</span><span class=\"n\">DataLoader</span><span class=\"p\">(</span><span class=\"n\">dataset</span><span class=\"o\">=</span><span class=\"n\">test_dataset</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">batch_size</span><span class=\"p\">,</span>\n <span class=\"n\">shuffle</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">MLP</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 定义损失函数和优化器\n</span><span class=\"n\">criterion</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">CrossEntropyLoss</span><span class=\"p\">()</span>\n<span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">optim</span><span class=\"p\">.</span><span class=\"n\">Adam</span><span class=\"p\">(</span><span class=\"n\">model</span><span class=\"p\">.</span><span class=\"n\">parameters</span><span class=\"p\">(),</span> <span class=\"n\">lr</span><span class=\"o\">=</span><span class=\"n\">learning_rate</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 训练模型\n</span><span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_epochs</span><span class=\"p\">):</span>\n <span class=\"k\">for</span> <span class=\"n\">images</span><span class=\"p\">,</span> <span class=\"n\">labels</span> <span class=\"ow\">in</span> <span class=\"n\">train_loader</span><span class=\"p\">:</span>\n <span class=\"c1\"># 前向传播\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">(</span><span class=\"n\">images</span><span class=\"p\">)</span>\n <span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">criterion</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 反向传播\n</span> <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">zero_grad</span><span class=\"p\">()</span>\n <span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">backward</span><span class=\"p\">()</span>\n <span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">step</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 输出训练损失\n</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">'Epoch </span><span class=\"si\">{</span><span class=\"n\">epoch</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"si\">}</span><span class=\"s\">, Training Loss: </span><span class=\"si\">{</span><span class=\"n\">loss</span><span class=\"p\">.</span><span class=\"n\">item</span><span class=\"p\">():.</span><span class=\"mi\">4</span><span class=\"n\">f</span><span class=\"si\">}</span><span class=\"s\">'</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>这段代码里,我们能看到 MLP 的模型定义是:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">ReLU</span><span class=\"p\">()</span>\n<span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Linear</span><span class=\"p\">(</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"n\">num_classes</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>与前面的模型示例代码类似,也都用到了反向传播、损失函数评价器、优化器。如果用公式表示的话,就是如下的模型定义:</p>\n\n\\[\\begin{aligned}\n&z = W_1 \\cdot x + b_1 \\\\\n&h = ReLU(z) \\\\\n&y = W_2 \\cdot h + b_2\n\\end{aligned}\\]\n\n<p>我们知道 MLP 通常是一个输入和输出长度相同的模型,但少数情况下也可以构建输入和输出长度不同的 MLP 模型,比如输入一组序列后,输出是一个离散的分类结果。</p>\n\n<h4 id=\"27简述如何训练一个模型正向传播与反向传播\">2.7、简述如何训练一个模型:正向传播与反向传播</h4>\n\n<p>这是个很重要的议题。但是春节时间有限,这部分只能简写了,我们更多聚焦在语言模型本身。这里简述一下,后续可能会再补全。</p>\n\n<ul>\n <li>训练神经网络,主要包括前向传播、反向传播这两步。</li>\n <li>正向传播,就是将数据输入给模型,基于已确定的一组参数(比如 MLP 中的权重 W、偏置 b 等),得到输出结果。根据输出结果计算损失函数,衡量当前参数下的模型性能。</li>\n <li>反向传播最常用到的是梯度下降法(这里不讨论其他方法),依托损失函数,将其中的参数当做变量来求偏导(计算梯度),沿着梯度下降的方向求解损失函数的极小值,此时的参数可替代此前的参数。这就是对模型优化训练的一个典型过程。</li>\n</ul>\n\n<p>引申问题 —— 梯度消失、梯度爆炸问题:因为对损失函数的求偏导,是从输出层向输入层反向基于「数学上的链式法则」计算的,数学上这是个连乘计算,层数越多越容易出现这个问题。这个求导过程可能会出现梯度为零的情况,即梯度消失。也有可能出现梯度值特别大的情况。</p>\n\n<p>解决梯度消失、梯度爆炸问题,又是一个重要议题,这里篇幅所限也难以展开做技术笔记。粗暴的方式比如梯度剪切,Hinton 提出的逐层预训练后再整体精调理论上也 work,本文后续提到的 LSTM、ResNet 等也可以解决问题,我们也还能了解到业界各种解决手段,有机会再与朋友们交流学习。</p>\n\n<h4 id=\"28mlp-的一个显著问题帮我们引出-cnn-模型\">2.8、MLP 的一个显著问题,帮我们引出 CNN 模型</h4>\n\n<p>我们可以看到,在 MLP 中,不论有多少层,某一层的输出向量 \\(h_n\\) 中的每个值,都会在下一层计算输出向量 \\(h_{n+1}\\) 的每个值时用到。具体来说,如果对于某一层的输出值如下:</p>\n\n\\[h_{n+1} = Softmax(W_{n+1} \\cdot h_n + b_{n+1})\\]\n\n<p>上一段话里所谓的「用到」,其实就是要针对 \\(h_n\\) 生成相应的特征值 \\(W_{n+1}\\) 权重矩阵中的每个行列里的数值和 \\(b_{n+1}\\) 偏差向量 里的每个值。如果用图画出来,就是:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-1b1299448dc08c90d29bebf8b1f045c1\" width=\"428pt\" height=\"116pt\" viewBox=\"0.00 0.00 427.64 116.00\">\n<title>graphviz-1b1299448dc08c90d29bebf8b1f045c1</title>\n<desc>\ndigraph G {\n\trankdir=TB\n\ta[label="..."]\n\tb[label="..."]\n\th_2_1[label="h_n+1_1"]\n\th_2_2[label="h_n+1_2"]\n\th_2_m[label="h_n+1_m"]\n\n\t{rank=same h_n_1 h_n_2 b h_n_m}\n\t{rank=same h_2_1 h_2_2 a h_2_m}\n\n\th_n_1 -> h_2_1\n\th_n_1 -> h_2_2\n\th_n_1 -> a\n\th_n_1 -> h_2_m\n\n\th_n_1 -> h_2_1\n\th_n_2 -> h_2_2\n\th_n_2 -> a\n\th_n_2 -> h_2_m\n\n\tb -> h_2_1\n\tb -> h_2_2\n\tb -> a\n\tb -> h_2_m\n\n\th_n_m -> h_2_1\n\th_n_m -> h_2_2\n\th_n_m -> a\n\th_n_m -> h_2_m\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-112 423.64,-112 423.64,4 -4,4\" />\n<!-- a -->\n<g id=\"node1\" class=\"node\">\n<title>a</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"146.7\" cy=\"-18\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"146.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b -->\n<g id=\"node2\" class=\"node\">\n<title>b</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"151.7\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"151.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- b->a -->\n<g id=\"edge11\" class=\"edge\">\n<title>b->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M150.46,-71.7C149.91,-63.98 149.25,-54.71 148.63,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"152.12,-45.83 147.92,-36.1 145.14,-46.33 152.12,-45.83\" />\n</g>\n<!-- h_2_1 -->\n<g id=\"node3\" class=\"node\">\n<title>h_2_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"50.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"50.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_1</text>\n</g>\n<!-- b->h_2_1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>b->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M133.64,-76.49C119.14,-66.44 98.46,-52.11 81.38,-40.27\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"83.04,-37.16 72.83,-34.34 79.05,-42.91 83.04,-37.16\" />\n</g>\n<!-- h_2_2 -->\n<g id=\"node4\" class=\"node\">\n<title>h_2_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"242.7\" cy=\"-18\" rx=\"50.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"242.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_2</text>\n</g>\n<!-- b->h_2_2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>b->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M168.81,-75.83C181.67,-65.94 199.56,-52.18 214.52,-40.67\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"216.69,-43.42 222.48,-34.55 212.42,-37.87 216.69,-43.42\" />\n</g>\n<!-- h_2_m -->\n<g id=\"node5\" class=\"node\">\n<title>h_2_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"365.7\" cy=\"-18\" rx=\"53.89\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"365.7\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n+1_m</text>\n</g>\n<!-- b->h_2_m -->\n<g id=\"edge12\" class=\"edge\">\n<title>b->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M172.78,-78.39C177.62,-76.14 182.79,-73.88 187.7,-72 211.14,-63.03 271.93,-45.36 315.95,-32.9\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"316.96,-36.25 325.63,-30.16 315.05,-29.51 316.96,-36.25\" />\n</g>\n<!-- h_n_1 -->\n<g id=\"node6\" class=\"node\">\n<title>h_n_1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"69.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"69.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_1</text>\n</g>\n<!-- h_n_1->a -->\n<g id=\"edge3\" class=\"edge\">\n<title>h_n_1->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M86.4,-73.81C97.36,-63.85 111.83,-50.7 123.85,-39.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"126.28,-42.29 131.33,-32.97 121.57,-37.11 126.28,-42.29\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M59.35,-72.41C56.39,-64.62 53.56,-55.14 51.51,-46.33\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"54.92,-45.55 49.5,-36.45 48.06,-46.94 54.92,-45.55\" />\n</g>\n<!-- h_n_1->h_2_1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h_n_1->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M70.91,-71.7C69.57,-63.7 67.15,-54.02 64.35,-45.15\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"67.63,-43.93 61.05,-35.62 61.01,-46.22 67.63,-43.93\" />\n</g>\n<!-- h_n_1->h_2_2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h_n_1->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.49,-77.75C125.45,-66.44 168.9,-48.86 200.99,-35.87\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"202.6,-39 210.56,-32 199.97,-32.51 202.6,-39\" />\n</g>\n<!-- h_n_1->h_2_m -->\n<g id=\"edge4\" class=\"edge\">\n<title>h_n_1->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M97.68,-77.83C103.57,-75.71 109.79,-73.65 115.7,-72 197.22,-49.25 220.23,-55.04 302.7,-36 307.03,-35 311.53,-33.9 316.02,-32.77\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"317.01,-36.13 325.81,-30.24 315.26,-29.35 317.01,-36.13\" />\n</g>\n<!-- h_n_2 -->\n<g id=\"node7\" class=\"node\">\n<title>h_n_2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"331.7\" cy=\"-90\" rx=\"37.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"331.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_2</text>\n</g>\n<!-- h_n_2->a -->\n<g id=\"edge7\" class=\"edge\">\n<title>h_n_2->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.01,-78.31C297.28,-76.2 291.3,-74.02 285.7,-72 240.06,-55.59 227.57,-54.38 182.7,-36 180.87,-35.25 179.01,-34.46 177.14,-33.65\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.4,-30.38 167.85,-29.44 175.52,-36.75 178.4,-30.38\" />\n</g>\n<!-- h_n_2->h_2_2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h_n_2->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M312.82,-74.15C300.65,-64.58 284.6,-51.96 270.93,-41.21\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"272.8,-38.23 262.78,-34.8 268.48,-43.73 272.8,-38.23\" />\n</g>\n<!-- h_n_2->h_2_m -->\n<g id=\"edge8\" class=\"edge\">\n<title>h_n_2->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M339.75,-72.41C343.72,-64.25 348.59,-54.22 353.04,-45.07\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"356.24,-46.48 357.46,-35.96 349.94,-43.42 356.24,-46.48\" />\n</g>\n<!-- h_n_m -->\n<g id=\"node8\" class=\"node\">\n<title>h_n_m</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"236.7\" cy=\"-90\" rx=\"40.09\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"236.7\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h_n_m</text>\n</g>\n<!-- h_n_m->a -->\n<g id=\"edge15\" class=\"edge\">\n<title>h_n_m->a</title>\n<path fill=\"none\" stroke=\"black\" d=\"M217.17,-73.81C203.86,-63.46 186.11,-49.66 171.76,-38.49\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"173.8,-35.65 163.76,-32.27 169.5,-41.17 173.8,-35.65\" />\n</g>\n<!-- h_n_m->h_2_1 -->\n<g id=\"edge13\" class=\"edge\">\n<title>h_n_m->h_2_1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M206.81,-77.75C176.21,-66.24 128.35,-48.22 93.68,-35.18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"94.87,-31.89 84.28,-31.64 92.41,-38.44 94.87,-31.89\" />\n</g>\n<!-- h_n_m->h_2_2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>h_n_m->h_2_2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M238.18,-71.7C238.84,-63.98 239.63,-54.71 240.37,-46.11\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"243.86,-46.37 241.23,-36.1 236.89,-45.77 243.86,-46.37\" />\n</g>\n<!-- h_n_m->h_2_m -->\n<g id=\"edge16\" class=\"edge\">\n<title>h_n_m->h_2_m</title>\n<path fill=\"none\" stroke=\"black\" d=\"M261.26,-75.67C280.58,-65.19 307.78,-50.43 329.57,-38.6\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"331.42,-41.58 338.54,-33.73 328.08,-35.43 331.42,-41.58\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>可以看到,输入的所有元素都被连接,即被分配权重 w 和偏差项 b,所以这被称为一个「全连接层(<strong>Fully Connected Layer</strong>)」或者「<strong>稠密层(Dense Layer)</strong>」。但是对于一些任务这样做是很蠢的,会付出大量无效的计算。</p>\n\n<p>因此我们需要 focus 在更少量计算成本的模型,于是有了卷积神经网络(CNN)。</p>\n\n<h3 id=\"第-3-节--卷积神经网络cnn\">第 3 节 · 卷积神经网络(CNN)</h3>\n\n<p>MLP 里每一层的每个元素,都要乘以一个独立参数的权重 W,再加上一个偏执 b,这样的神经网络层常被我们叫做「全连接层(Fully Connected Layer)或稠密层(Dence Layer)。但是这样有个显著问题:如果输入内容的局部重要信息只是发生轻微移动并没有丢失,在全连接层处理后,整个输出结果都会发生很大变化 —— 这不合理。</p>\n\n<p>于是我们会想到,如果我们用一个小一些的全连接层,只对重要的局部输入进行处理呢?其实这个思路和 n-gram 是类似的,都是用一个窗口来扫描局部。卷积神经网络(Convolutional Neural Network,CNN)就是基于此诞生的。</p>\n\n<ul>\n <li>卷积核:卷积核是一个小的稠密层,用于提取局部特征,又称其为卷积核(kernel)/ 滤波器(filter)/ 感受野(receptive field / field of view)。</li>\n <li>池化层(Pooling,或称汇聚层):经过卷积核处理的结果,进一步聚合的过程。对于输入大小不一样的样本,池化后将有相同个数的特征输出。</li>\n <li>提取多个局部特征:一个卷积核只能提取单一类型的局部特征,需要提取多种局部特征则需要多个卷积核。有些文章里你看提到「多个模式」、「多个通道」,其实指的就是多个 kernel 识别多个特征。</li>\n <li>全连接分类层:多个卷积核得到的多个特征,需经过一个全连接的分类层用于最终决策。</li>\n</ul>\n\n<p>这样做有几个特性:</p>\n\n<ul>\n <li>本地性(Locality):输出结果只由一个特定窗口大小区域内的数据决定。</li>\n <li>平移不变性(Translation Invariant):对同一个特征,扫描不同区域时只用一个 kernel 来计算。</li>\n <li>卷积层的参数规模,与输入输出数据大小无关。</li>\n</ul>\n\n<p>CNN 主要的适用领域是计算机视觉。而在 NLP 中,文本数据的维度很高,并且语言的结构比图像更复杂。因此,CNN 一般不适用于处理 NLP 问题。</p>\n\n<h3 id=\"第-4-节--循环神经网络rnn\">第 4 节 · 循环神经网络(RNN)</h3>\n\n<p>RNN(循环神经网络),这是一种强大的神经网络模型,能够预测序列数据,例如文本、语音和时间序列。我们将通过生动的代码示例和实际案例来演示如何使用 RNN,并在日常生活中真实地体验它的功能。您将学习到如何使用 RNN 解决各种机器学习问题,并动手尝试运用 RNN 解决实际问题。这篇文章将为您提供一个完整的 RNN 入门指南,并使您对 RNN 有更深入的了解。</p>\n\n<p>RNN(Recurrent Neural Network)的 R 是 Recurrent 的意思,所以这是一个贷循环的神经网络。首先要明白一点,你并不需要搞懂 CNN 后才能学习 RNN 模型。你只要了解了 MLP 就可以学习 RNN 了。</p>\n\n<h4 id=\"41经典结构的-rnn\">4.1、经典结构的 RNN</h4>\n\n<p><img src=\"/img/src/2022-12-19-language-model-1.png\" alt=\"image\" /></p>\n\n<p>上图这是一个经典结构的 RNN 示意图,Unfold 箭头右侧是展开示意。输入序列(这里用 x 表示)传递给隐藏层(hidden layer,这里用 h 表示),处理完生成输出序列(这里用 o 表示)。序列的下一个词输入时的、上一步隐藏层会一起影响这一步的输出。U、V、W 都表示权重。在这个经典结构理,你可以看到非常重要的一点,就是输入序列长度与输出序列长度是相同的。</p>\n\n<p>这种经典结构的应用场景,比如对一段普通话输入它的四川话版本,比如对视频的每一帧进行处理并输出,等等。</p>\n\n<p>我们知道 RNN 是一个一个序列处理的,每个序列中的数据项都是有序的,所以对于计算一个序列内的所有数据项是无法并行的。但是计算不同序列时,不同序列各自的计算则是可以并行的。如果我们把上一个时刻 t 隐藏层输出的结果( \\(h_{t-1}\\) )传给一个激活函数(比如说用正切函数 <code class=\"language-plaintext highlighter-rouge\">tanh</code> 函数),然后和当下时刻 t 的这个输入( \\(x_{t}\\) )一起,处理后产生一个时刻 t 的输出( \\(h_t\\) )。然后把隐藏层的输出通过多项逻辑回归(Softmax)生成最终的输出值( \\(\\bm{y}\\) ),我们可以如下表示这个模型:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>对应的示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-34cd77ba92d6e898bab41a54b23f2324\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-34cd77ba92d6e898bab41a54b23f2324</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same x1 x2 xddd xn}\n\t{rank=same y1 y2 yddd yn}\n\txddd[label="..."]\n\tyddd[label="..."]\n\thddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node9\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node10\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge10\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node12\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge11\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node5\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node6\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node7\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node8\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>这种输入和输出数据项数一致的 RNN,一般叫做 N vs. N 的 RNN。如果我们用 PyTorch 来实现一个非常简单的经典 RNN 则如下:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"kn\">import</span> <span class=\"nn\">torch.nn</span> <span class=\"k\">as</span> <span class=\"n\">nn</span>\n\n<span class=\"c1\"># 创建一个 RNN 实例\n# 第一个参数\n</span><span class=\"n\">rnn</span> <span class=\"o\">=</span> <span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">RNN</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">batch_first</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span> <span class=\"c1\"># 实例化一个单向单层RNN\n</span>\n<span class=\"c1\"># 输入是一个形状为 (5, 3, 10) 的张量\n# 5 个输入数据项(也可以说是样本)\n# 3 个数据项是一个序列,有 3 个 steps\n# 每个 step 有 10 个特征\n</span><span class=\"nb\">input</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 隐藏层是一个 (1, 5, 20) 的张量\n</span><span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 调用 rnn 函数后,返回输出、最终的隐藏状态\n</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">hn</span> <span class=\"o\">=</span> <span class=\"n\">rnn</span><span class=\"p\">(</span><span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">)</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">hn</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>我们来解读一下这段代码:</p>\n\n<ul>\n <li>这段代码实例化了一个带有 1 个隐藏层的 RNN 网络。</li>\n <li>它的输入是一个形状为 (5, 3, 10) 的张量,表示有 5 个样本,每个样本有 3 个时间步,每个时间步的特征维度是 10。</li>\n <li>初始隐藏状态是一个形状为 (1, 5, 20) 的张量。</li>\n <li>调用 rnn 函数后,会返回输出和最终的隐藏状态。</li>\n <li>输出的形状是 (5, 3, 20),表示有 5 个样本,每个样本有 3 个时间步,每个时间步的输出维度是 20。</li>\n <li>最终的隐藏状态的形状是 (1, 5, 20),表示最后的隐藏状态是 5</li>\n</ul>\n\n<p>但是上面的代码示例,并没有自己编写一个具体的 RNN,而是用了默认的 PyTorch 的 RNN,那么下面我们就自己编写一个:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">class</span> <span class=\"nc\">MikeCaptainRNN</span><span class=\"p\">(</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">Module</span><span class=\"p\">):</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">input_size</span><span class=\"p\">,</span> <span class=\"n\">hidden_size</span><span class=\"p\">):</span>\n <span class=\"nb\">super</span><span class=\"p\">().</span><span class=\"n\">__init__</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 对于 RNN,输入维度就是序列数\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"n\">input_size</span>\n\n <span class=\"c1\"># 隐藏层有多少个节点/神经元,经常将 hidden_size 设置为与序列长度相同\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span> <span class=\"o\">=</span> <span class=\"n\">hidden_size</span>\n\n <span class=\"c1\"># 输入层到隐藏层的 W^{xh} 权重、bias^{xh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">input_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_xh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 隐藏层到隐藏层的 W^{hh} 权重、bias^{hh} 偏置项\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"mf\">0.01</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">randn</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前向传播\n</span> <span class=\"k\">def</span> <span class=\"nf\">forward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"nb\">input</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 取出这个张量的形状\n</span> <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"n\">input_size</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">.</span><span class=\"n\">shape</span>\n\n <span class=\"c1\"># 初始化一个全零张量\n</span> <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">L</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">hidden_size</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 处理每个时刻的输入特征\n</span> <span class=\"k\">for</span> <span class=\"n\">t</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">L</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 获得当前时刻的输入特征,[N, input_size, 1]。unsqueeze(n),在第 n 维上增加一维\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:].</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span> \n <span class=\"n\">w_xh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_xh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, input_size]\n</span> <span class=\"n\">w_hh_batch</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">weight_hh</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">).</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size, hidden_size]\n</span>\n <span class=\"c1\"># bmm 是矩阵乘法函数\n</span> <span class=\"n\">w_times_x</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_xh_batch</span><span class=\"p\">,</span> <span class=\"n\">x</span><span class=\"p\">).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]。squeeze(n),在第n维上减小一维\n</span> <span class=\"n\">w_times_h</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">bmm</span><span class=\"p\">(</span><span class=\"n\">w_hh_batch</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)).</span><span class=\"n\">squeeze</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"c1\"># [N, hidden_size]\n</span> <span class=\"n\">h0</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tanh</span><span class=\"p\">(</span><span class=\"n\">w_times_x</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_ih</span> <span class=\"o\">+</span> <span class=\"n\">w_times_h</span> <span class=\"o\">+</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bias_hh</span><span class=\"p\">)</span>\n <span class=\"n\">output</span><span class=\"p\">[:,</span> <span class=\"n\">t</span><span class=\"p\">,</span> <span class=\"p\">:]</span> <span class=\"o\">=</span> <span class=\"n\">h0</span>\n <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"n\">h0</span><span class=\"p\">.</span><span class=\"n\">unsqueeze</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>源码解读都在注释中。</p>\n\n<h4 id=\"42n-vs1-的-rnn\">4.2、N vs.1 的 RNN</h4>\n\n<p>上面那个图里,如果只保留最后一个输出,那就是一个 N vs. 1 的 RNN 了。这种的应用场景,比如说判断一个文本序列是英语还是德语,比如根据一个输入序列来判断是一个正向情绪内容还是负向或者中性,或者比如根据一段语音输入序列来判断是哪一首曲子(听歌识曲)。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = tanh(\\bm{W^{xh}} \\cdot \\bm{x}_t + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) \\\\\n&\\bm{y} = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_n + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>即这个模型里,每个序列只有隐藏层对最后一个数据项进行处理时才产生输出 \\(h_n\\) 如果用示意图表示,则是如下结构:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-99506286249ff03a109fde8e4294e12c\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-99506286249ff03a109fde8e4294e12c</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\thddd[label="..."]\n\txddd[label="..."]\n\n\ty[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx1 -> h1\n\tx2 -> h2\n\txn -> hn\n\txddd -> hddd\n\n\thn -> y\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- y -->\n<g id=\"node6\" class=\"node\">\n<title>y</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y</text>\n</g>\n<!-- hn->y -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->y</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node7\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node8\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node9\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge6\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h4 id=\"431-vs-n-的-rnn\">4.3、1 vs. N 的 RNN</h4>\n\n<p>反过来,上面那个图里,如果只保留一个 x,那么就是一个 1 vs. N 的 RNN 了。这种场景的应用,比如 AI 创作音乐,还有通过一个 image 提炼或识别某些文本内容输出。</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\begin{cases} tanh(\\bm{W^{xh}} \\cdot \\bm{x} + \\bm{b^{xh}} + 0 + \\bm{b^{hh}}) & (t=1) \\\\\ntanh(0 + \\bm{b^{xh}} + \\bm{W^{hh}} \\cdot \\bm{h}_{t-1} + \\bm{b^{hh}}) & (t>1) \\end{cases} \\\\\n&\\bm{y}_t = Softmax(\\bm{W^{hy}} \\cdot \\bm{h}_t + \\bm{b^{hy}})\n\\end{aligned}\\]\n\n<p>示意图如下:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-aecb5ea5cd91fc1106b18c3c4059fa0a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\t{rank=same y1 y2 yddd yn}\n\thddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx[shape=plaintext]\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\tx -> h1\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node5\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node6\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node7\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node8\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- x -->\n<g id=\"node9\" class=\"node\">\n<title>x</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x</text>\n</g>\n<!-- x->h1 -->\n<g id=\"edge4\" class=\"edge\">\n<title>x->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>到这里我们可以看到,在 RNN 的隐藏层是能够存储一些有关于输入数据的一些相关内容的,所以也常把 RNN 的隐藏层叫做记忆单元。</p>\n\n<h4 id=\"44lstmlong-short-term-memory长短时记忆网络\">4.4、LSTM(Long Short-Term Memory)长短时记忆网络</h4>\n\n<h5 id=\"441如何理解这个-short-term-呢\">4.4.1、如何理解这个 Short-Term 呢?</h5>\n\n<p>1997 年论文《Long Short-Term Memory》中提出 LSTM 模型。我们先从模型的定义,精确地来理解一下:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_t = \\bm{h}_{t-1} + tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<p>上式中与经典结构的 RNN(输入与输出是 N vs. N)相比,唯一的区别是第一个式子中多了一个「 \\(\\bm{h}_{t-1}\\) 」。如果我们把第一个式子的 \\(tanh\\) 部分记作 \\(u_t\\) :</p>\n\n\\[\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh})\\]\n\n<p>所以:</p>\n\n\\[\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\]\n\n<p>那么可以展开出如下一组式子:</p>\n\n\\[\\begin{aligned}\n\\bm{h}_{k+1} &= \\bm{h}_k + \\bm{u}_{k+1} \\\\\n\\bm{h}_{k+2} &= \\bm{h}_{k+1} + \\bm{u}_{k+2} \\\\\n&...... \\\\\n\\bm{h}_{t-1} &= \\bm{h}_{t-2} + \\bm{u}_{t-1} \\\\\n\\bm{h}_t &= \\bm{h}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>如果我们从 \\(h_{k+1}\\) 到 \\(h_n\\) 的所有式子左侧相加、右侧相加,我们就得到如下式子:</p>\n\n\\[\\begin{aligned}\n&\\bm{h}_{k+1} + ... + \\bm{h}_{t-1} + \\bm{h}_t \\\\\n= &\\bm{h}_k + \\bm{h}_{k+1} + ... + \\bm{h}_{t-2} + \\bm{h}_{t-1} \\\\+ &\\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\n\\end{aligned}\\]\n\n<p>进而推导出:</p>\n\n\\[\\bm{h}_t = \\bm{h}_k + \\bm{u}_{k+1} + \\bm{u}_{k+2} + ... + \\bm{u}_{t-1} + \\bm{u}_t\\]\n\n<p>从这里我们就可以看到,第 t 时刻的隐藏层输出,直接关联到第 k 时刻的输出,t 到 k 时刻的相关性则用 \\(\\bm{u}_{k+1}\\) 到 \\(\\bm{u}_t\\) 相加表示。也就是有 t-k 的短期(Short Term)记忆。</p>\n\n<h5 id=\"442引入遗忘门-f输入门-i输出门-o记忆细胞-c\">4.4.2、引入遗忘门 f、输入门 i、输出门 o、记忆细胞 c</h5>\n\n<p>如果我们为式子 \\(\\bm{h}_t = \\bm{h}_{t-1} + \\bm{u}_t\\) 右侧两项分配一个权重呢?就是隐藏层对上一个数据项本身被上一个数据项经过隐藏层计算的结果,这两者做一对权重考虑配比,如下:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + (1 - \\bm{f}_t) \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n\n<ul>\n <li>\\(\\odot\\) 是 Hardamard 乘积,即张量的对应元素相乘。</li>\n <li>\\(\\bm{f}_t\\) 是「遗忘门(Forget Gate)」,该值很小时 t-1 时刻的权重就很小,也就是「此刻遗忘上一刻」。该值应根据 t 时刻的输入数据、t-1 时刻数据在隐藏层的输出计算,而且其每个元素必须是 (0, 1) 之间的值,所以可以用 sigmoid 函数来得到该值:</li>\n</ul>\n\n<p>但这种方式,对于过去 \\(\\bm{h}_{t-1}\\) 和当下 \\(\\bm{u}_t\\) 形成了互斥,只能此消彼长。但其实过去和当下可能都很重要,有可能都恨不重要,所以我们对过去继续采用 \\(\\bm{f}_t\\) 遗忘门,对当下采用 \\(\\bm{i}_t\\) 输入门(Input Gate):</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{h}_t = \\bm{f}_t \\odot \\bm{h}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\n\\end{aligned}\\]\n\n<p>其中:</p>\n<ul>\n <li>与 \\(\\bm{f}_t\\) 类似地,定义输入门 \\(\\bm{i}_t\\) ,但是注意 \\(\\bm{f}_t\\) 与 \\(\\bm{h}_{t-1}\\) 而非 \\(\\bm{x}_{t-1}\\) 有关。</li>\n</ul>\n\n<p>再引入一个输出门:</p>\n\n\\[\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh})\\]\n\n<p>再引入记忆细胞 \\(\\bm{c}_t\\) ,它是原来 \\(\\bm{h}_t\\) 的变体,与 t-1 时刻的记忆细胞有遗忘关系(通过遗忘门),与当下时刻有输入门的关系:</p>\n\n\\[\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t\\]\n\n<p>那么此时 \\(\\bm{h}_t\\) ,我们可以把 \\(\\bm{h}_t\\) 变成:</p>\n\n\\[\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t)\\]\n\n<p>记忆细胞这个概念还有有一点点形象的,它存储了过去的一些信息。OK,到此我们整体的 LSTM 模型就变成了这个样子:</p>\n\n\\[\\begin{aligned}\n&\\bm{f}_t = sigmoid(\\bm{W}^{f,xh} \\cdot \\bm{x}_t + \\bm{b}^{f,xh} + \\bm{W}^{f,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{f,hh}) \\\\\n&\\bm{i}_t = sigmoid(\\bm{W}^{i,xh} \\cdot \\bm{x}_t + \\bm{b}^{i,xh} + \\bm{W}^{i,hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{i,hh}) \\\\\n&\\bm{o}_t = sigmoid(\\bm{W}^{o,xh} \\cdot \\bm{x}_t + \\bm{b}^{o,xh} + \\bm{W}^{o,hh} \\cdot \\bm{x}_{t-1} + \\bm{b}^{o,hh}) \\\\\n&\\bm{u}_t = tanh(\\bm{W}^{xh} \\cdot \\bm{x}_t + \\bm{b}^{xh} + \\bm{W}^{hh} \\cdot \\bm{h}_{t-1} + \\bm{b}^{hh}) \\\\\n&\\bm{c}_t = \\bm{f}_t \\odot \\bm{c}_{t-1} + \\bm{i}_t \\odot \\bm{u}_t \\\\\n&\\bm{h}_t = \\bm{o}_t \\odot tanh(\\bm{c}_t) \\\\\n&\\bm{y}_t = Softmax(\\bm{W}^{hy} \\cdot \\bm{h_t} + \\bm{b}^{hy})\n\\end{aligned}\\]\n\n<h4 id=\"45双向循环神经网络双向-lstm\">4.5、双向循环神经网络、双向 LSTM</h4>\n\n<p>双向循环神经网络很好理解,就是两个方向都有,例如下图:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-5d130f67fc1bf07abc38d62da6ddb01a\" width=\"278pt\" height=\"188pt\" viewBox=\"0.00 0.00 278.00 188.00\">\n<title>graphviz-5d130f67fc1bf07abc38d62da6ddb01a</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h1 h2 hddd hn}\n\n\thddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th1 -> y1\n\th2 -> y2\n\thddd -> yddd\n\thn -> yn\n\n\th1 -> h2\n\th2 -> hddd\n\thddd -> hn\n\n\thn -> hddd\n\thddd -> h2\n\th2 -> h1\n\n\tx1 -> h1\n\tx2 -> h2\n\txddd -> hddd\n\txn -> hn\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 274,-184 274,4 -4,4\" />\n<!-- h1 -->\n<g id=\"node1\" class=\"node\">\n<title>h1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1</text>\n</g>\n<!-- h2 -->\n<g id=\"node2\" class=\"node\">\n<title>h2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2</text>\n</g>\n<!-- h1->h2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h1->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M48.38,-101.27C54.78,-103.22 61.18,-103.89 67.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"68.52,-106.66 77.64,-101.27 67.15,-99.8 68.52,-106.66\" />\n</g>\n<!-- y1 -->\n<g id=\"node7\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h1->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-108.3C27,-116.02 27,-125.29 27,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-133.9 27,-143.9 30.5,-133.9 23.5,-133.9\" />\n</g>\n<!-- h2->h1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M77.64,-78.73C71.24,-76.78 64.84,-76.11 58.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"57.49,-73.34 48.38,-78.73 58.87,-80.2 57.49,-73.34\" />\n</g>\n<!-- hddd -->\n<g id=\"node3\" class=\"node\">\n<title>hddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h2->hddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h2->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M120.38,-101.27C126.78,-103.22 133.18,-103.89 139.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.52,-106.66 149.64,-101.27 139.15,-99.8 140.52,-106.66\" />\n</g>\n<!-- y2 -->\n<g id=\"node8\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h2->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-108.3C99,-116.02 99,-125.29 99,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-133.9 99,-143.9 102.5,-133.9 95.5,-133.9\" />\n</g>\n<!-- hddd->h2 -->\n<g id=\"edge9\" class=\"edge\">\n<title>hddd->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M149.64,-78.73C143.24,-76.78 136.84,-76.11 130.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"129.49,-73.34 120.38,-78.73 130.87,-80.2 129.49,-73.34\" />\n</g>\n<!-- hn -->\n<g id=\"node4\" class=\"node\">\n<title>hn</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">hn</text>\n</g>\n<!-- hddd->hn -->\n<g id=\"edge7\" class=\"edge\">\n<title>hddd->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M192.38,-101.27C198.78,-103.22 205.18,-103.89 211.58,-103.28\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.52,-106.66 221.64,-101.27 211.15,-99.8 212.52,-106.66\" />\n</g>\n<!-- yddd -->\n<g id=\"node6\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- hddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>hddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-108.3C171,-116.02 171,-125.29 171,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-133.9 171,-143.9 174.5,-133.9 167.5,-133.9\" />\n</g>\n<!-- hn->hddd -->\n<g id=\"edge8\" class=\"edge\">\n<title>hn->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M221.64,-78.73C215.24,-76.78 208.84,-76.11 202.44,-76.72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"201.49,-73.34 192.38,-78.73 202.87,-80.2 201.49,-73.34\" />\n</g>\n<!-- yn -->\n<g id=\"node9\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- hn->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>hn->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-108.3C243,-116.02 243,-125.29 243,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-133.9 243,-143.9 246.5,-133.9 239.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node5\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->hddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>xddd->hddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node10\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h1 -->\n<g id=\"edge11\" class=\"edge\">\n<title>x1->h1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node11\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h2 -->\n<g id=\"edge12\" class=\"edge\">\n<title>x2->h2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node12\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->hn -->\n<g id=\"edge14\" class=\"edge\">\n<title>xn->hn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code> – If True, becomes a bidirectional RNN. Default: False</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">bidirectional</code>:默认设置为 <code class=\"language-plaintext highlighter-rouge\">False</code>。若为 <code class=\"language-plaintext highlighter-rouge\">True</code>,即为双向 RNN。</p>\n\n<h4 id=\"46堆叠循环神经网络stacked-rnn堆叠长短时记忆网络stacked-lstm\">4.6、堆叠循环神经网络(Stacked RNN)、堆叠长短时记忆网络(Stacked LSTM)</h4>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-79478ee70f94925103b38a21c70c2539\" width=\"288pt\" height=\"260pt\" viewBox=\"0.00 0.00 288.19 260.00\">\n<title>graphviz-79478ee70f94925103b38a21c70c2539</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same h11 h12 h1ddd h1n}\n\t{rank=same h21 h22 h2ddd h2n}\n\n\th1ddd[label="..."]\n\th2ddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\n\th11 -> y1\n\th12 -> y2\n\th1ddd -> yddd\n\th1n -> yn\n\n\th11 -> h12\n\th12 -> h1ddd\n\th1ddd -> h1n\n\n\th21 -> h22\n\th22 -> h2ddd\n\th2ddd -> h2n\n\n\th21 -> h11\n\th22 -> h12\n\th2ddd -> h1ddd\n\th2n -> h1n\n\n\tx1 -> h21\n\tx2 -> h22\n\txddd -> h2ddd\n\txn -> h2n\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-256 284.19,-256 284.19,4 -4,4\" />\n<!-- h11 -->\n<g id=\"node1\" class=\"node\">\n<title>h11</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h11</text>\n</g>\n<!-- h12 -->\n<g id=\"node2\" class=\"node\">\n<title>h12</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h12</text>\n</g>\n<!-- h11->h12 -->\n<g id=\"edge5\" class=\"edge\">\n<title>h11->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-162C59.75,-162 62.19,-162 64.63,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-165.5 74.67,-162 64.67,-158.5 64.67,-165.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node11\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- h11->y1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>h11->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-180.3C28.6,-188.02 28.6,-197.29 28.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-205.9 28.6,-215.9 32.1,-205.9 25.1,-205.9\" />\n</g>\n<!-- h1ddd -->\n<g id=\"node3\" class=\"node\">\n<title>h1ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-162\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h12->h1ddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>h12->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-162C134.85,-162 137.49,-162 140.13,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-165.5 150.3,-162 140.3,-158.5 140.3,-165.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node12\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- h12->y2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>h12->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-180.3C103.6,-188.02 103.6,-197.29 103.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-205.9 103.6,-215.9 107.1,-205.9 100.1,-205.9\" />\n</g>\n<!-- h1n -->\n<g id=\"node4\" class=\"node\">\n<title>h1n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-162\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">h1n</text>\n</g>\n<!-- h1ddd->h1n -->\n<g id=\"edge7\" class=\"edge\">\n<title>h1ddd->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-162C207.38,-162 210,-162 212.61,-162\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-165.5 222.7,-162 212.7,-158.5 212.7,-165.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node10\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h1ddd->yddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>h1ddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-180.3C177.6,-188.02 177.6,-197.29 177.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-205.9 177.6,-215.9 181.1,-205.9 174.1,-205.9\" />\n</g>\n<!-- yn -->\n<g id=\"node13\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- h1n->yn -->\n<g id=\"edge4\" class=\"edge\">\n<title>h1n->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-180.3C251.6,-188.02 251.6,-197.29 251.6,-205.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-205.9 251.6,-215.9 255.1,-205.9 248.1,-205.9\" />\n</g>\n<!-- h21 -->\n<g id=\"node5\" class=\"node\">\n<title>h21</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"28.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h21</text>\n</g>\n<!-- h21->h11 -->\n<g id=\"edge11\" class=\"edge\">\n<title>h21->h11</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-108.3C28.6,-116.02 28.6,-125.29 28.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-133.9 28.6,-143.9 32.1,-133.9 25.1,-133.9\" />\n</g>\n<!-- h22 -->\n<g id=\"node6\" class=\"node\">\n<title>h22</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"103.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h22</text>\n</g>\n<!-- h21->h22 -->\n<g id=\"edge8\" class=\"edge\">\n<title>h21->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M57.31,-90C59.75,-90 62.19,-90 64.63,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"64.67,-93.5 74.67,-90 64.67,-86.5 64.67,-93.5\" />\n</g>\n<!-- h22->h12 -->\n<g id=\"edge12\" class=\"edge\">\n<title>h22->h12</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-108.3C103.6,-116.02 103.6,-125.29 103.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-133.9 103.6,-143.9 107.1,-133.9 100.1,-133.9\" />\n</g>\n<!-- h2ddd -->\n<g id=\"node7\" class=\"node\">\n<title>h2ddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"177.6\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- h22->h2ddd -->\n<g id=\"edge9\" class=\"edge\">\n<title>h22->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M132.21,-90C134.85,-90 137.49,-90 140.13,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"140.3,-93.5 150.3,-90 140.3,-86.5 140.3,-93.5\" />\n</g>\n<!-- h2ddd->h1ddd -->\n<g id=\"edge13\" class=\"edge\">\n<title>h2ddd->h1ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-108.3C177.6,-116.02 177.6,-125.29 177.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-133.9 177.6,-143.9 181.1,-133.9 174.1,-133.9\" />\n</g>\n<!-- h2n -->\n<g id=\"node8\" class=\"node\">\n<title>h2n</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"251.6\" cy=\"-90\" rx=\"28.7\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">h2n</text>\n</g>\n<!-- h2ddd->h2n -->\n<g id=\"edge10\" class=\"edge\">\n<title>h2ddd->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M204.77,-90C207.38,-90 210,-90 212.61,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"212.7,-93.5 222.7,-90 212.7,-86.5 212.7,-93.5\" />\n</g>\n<!-- h2n->h1n -->\n<g id=\"edge14\" class=\"edge\">\n<title>h2n->h1n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-108.3C251.6,-116.02 251.6,-125.29 251.6,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-133.9 251.6,-143.9 255.1,-133.9 248.1,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node9\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"177.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->h2ddd -->\n<g id=\"edge17\" class=\"edge\">\n<title>xddd->h2ddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M177.6,-36.3C177.6,-44.02 177.6,-53.29 177.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"174.1,-61.9 177.6,-71.9 181.1,-61.9 174.1,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node14\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"28.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->h21 -->\n<g id=\"edge15\" class=\"edge\">\n<title>x1->h21</title>\n<path fill=\"none\" stroke=\"black\" d=\"M28.6,-36.3C28.6,-44.02 28.6,-53.29 28.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"25.1,-61.9 28.6,-71.9 32.1,-61.9 25.1,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node15\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"103.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->h22 -->\n<g id=\"edge16\" class=\"edge\">\n<title>x2->h22</title>\n<path fill=\"none\" stroke=\"black\" d=\"M103.6,-36.3C103.6,-44.02 103.6,-53.29 103.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"100.1,-61.9 103.6,-71.9 107.1,-61.9 100.1,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node16\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"251.6\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->h2n -->\n<g id=\"edge18\" class=\"edge\">\n<title>xn->h2n</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.6,-36.3C251.6,-44.02 251.6,-53.29 251.6,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"248.1,-61.9 251.6,-71.9 255.1,-61.9 248.1,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>在 PyTorch 中使用 <code class=\"language-plaintext highlighter-rouge\">nn.RNN</code> 就有参数表示双向:</p>\n\n<blockquote>\n <p>num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two RNNs together to form a stacked RNN, with the second RNN taking in outputs of the first RNN and computing the final results. Default: 1</p>\n</blockquote>\n\n<p><code class=\"language-plaintext highlighter-rouge\">num_layers</code>:隐藏层层数,默认设置为 1 层。当 <code class=\"language-plaintext highlighter-rouge\">num_layers</code> >= 2 时,就是一个 stacked RNN 了。</p>\n\n<h4 id=\"47n-vs-m-的-rnn\">4.7、N vs. M 的 RNN</h4>\n\n<p>对于输入序列长度(长度 N)和输出序列长度(长度 M)不一样的 RNN 模型结构,也可以叫做 Encoder-Decoder 模型,也可以叫 Seq2Seq 模型。首先接收输入序列的 Encoder 先将输入序列转成一个隐藏态的上下文表示 C。C 可以只与最后一个隐藏层有关,甚至可以是最后一个隐藏层生成的隐藏态直接设置为 C,C 还可以与所有隐藏层有关。</p>\n\n<p>有了这个 C 之后,再用 Decoder 进行解码,也就是从把 C 作为输入状态开始,生成输出序列。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-094de5e41d0af67d4c5617e0f04d7b57\" width=\"638pt\" height=\"188pt\" viewBox=\"0.00 0.00 638.00 188.00\">\n<title>graphviz-094de5e41d0af67d4c5617e0f04d7b57</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\t{rank=same e1 e2 eddd en C d1 d2 dddd dm}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tC[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tyn[shape=plaintext]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\ten -> C\n\tC -> d1\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdm -> yn\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dm\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-184 634,-184 634,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54,-90C56.61,-90 59.23,-90 61.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126,-90C128.61,-90 131.23,-90 133.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.93,-93.5 143.93,-90 133.93,-86.5 133.93,-93.5\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198,-90C200.61,-90 203.23,-90 205.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"205.93,-93.5 215.93,-90 205.93,-86.5 205.93,-93.5\" />\n</g>\n<!-- C -->\n<g id=\"node5\" class=\"node\">\n<title>C</title>\n<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n</g>\n<!-- en->C -->\n<g id=\"edge8\" class=\"edge\">\n<title>en->C</title>\n<path fill=\"none\" stroke=\"black\" d=\"M270,-90C272.61,-90 275.23,-90 277.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"277.93,-93.5 287.93,-90 277.93,-86.5 277.93,-93.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node6\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"387\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"387\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- C->d1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>C->d1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M342.28,-90C344.74,-90 347.19,-90 349.65,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"349.75,-93.5 359.75,-90 349.75,-86.5 349.75,-93.5\" />\n</g>\n<!-- d2 -->\n<g id=\"node7\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"459\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"459\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M414,-90C416.61,-90 419.23,-90 421.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"421.93,-93.5 431.93,-90 421.93,-86.5 421.93,-93.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node15\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"387\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M387,-108.3C387,-116.02 387,-125.29 387,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"383.5,-133.9 387,-143.9 390.5,-133.9 383.5,-133.9\" />\n</g>\n<!-- dddd -->\n<g id=\"node8\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"531\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"531\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M486,-90C488.61,-90 491.23,-90 493.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"493.93,-93.5 503.93,-90 493.93,-86.5 493.93,-93.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node16\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"459\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge11\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M459,-108.3C459,-116.02 459,-125.29 459,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"455.5,-133.9 459,-143.9 462.5,-133.9 455.5,-133.9\" />\n</g>\n<!-- dm -->\n<g id=\"node9\" class=\"node\">\n<title>dm</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"603\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"603\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">dm</text>\n</g>\n<!-- dddd->dm -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dm</title>\n<path fill=\"none\" stroke=\"black\" d=\"M558,-90C560.61,-90 563.23,-90 565.84,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"565.93,-93.5 575.93,-90 565.93,-86.5 565.93,-93.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node11\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"531\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge12\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531,-108.3C531,-116.02 531,-125.29 531,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"527.5,-133.9 531,-143.9 534.5,-133.9 527.5,-133.9\" />\n</g>\n<!-- yn -->\n<g id=\"node17\" class=\"node\">\n<title>yn</title>\n<text text-anchor=\"middle\" x=\"603\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">yn</text>\n</g>\n<!-- dm->yn -->\n<g id=\"edge13\" class=\"edge\">\n<title>dm->yn</title>\n<path fill=\"none\" stroke=\"black\" d=\"M603,-108.3C603,-116.02 603,-125.29 603,-133.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"599.5,-133.9 603,-143.9 606.5,-133.9 599.5,-133.9\" />\n</g>\n<!-- xddd -->\n<g id=\"node10\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-36.3C171,-44.02 171,-53.29 171,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-61.9 171,-71.9 174.5,-61.9 167.5,-61.9\" />\n</g>\n<!-- x1 -->\n<g id=\"node12\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-36.3C27,-44.02 27,-53.29 27,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-61.9 27,-71.9 30.5,-61.9 23.5,-61.9\" />\n</g>\n<!-- x2 -->\n<g id=\"node13\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-36.3C99,-44.02 99,-53.29 99,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-61.9 99,-71.9 102.5,-61.9 95.5,-61.9\" />\n</g>\n<!-- xn -->\n<g id=\"node14\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"243\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M243,-36.3C243,-44.02 243,-53.29 243,-61.89\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"239.5,-61.9 243,-71.9 246.5,-61.9 239.5,-61.9\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>具体地,可以如下表示:</p>\n\n\\[\\begin{aligned}\n&\\bm{C} = Encoder(\\bm{X}) \\\\\n&\\bm{Y} = Decoder(\\bm{C}) \\\\\n\\end{aligned}\\]\n\n<p>进一步展开:</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>这种的应用就非常广了,因为大多数时候输入序列与输出序列的长度都是不同的,比如最常见的应用「翻译」,从一个语言翻译成另一个语言;再比如 AI 的一个领域「语音识别」,将语音序列输入后生成所识别的文本内容;还有比如 ChatGPT 这种问答应用等等。</p>\n\n<p>Seq2Seq 模型非常出色,一直到 2018 年之前 NLP 领域里该模型已成为主流。但是它有很显著的问题:</p>\n\n<ul>\n <li>当输入序列很长时,Encoder 生成的 Context 可能就会出现所捕捉的信息不充分的情况,导致 Decoder 最终的输出是不尽如人意的。具体地,毕竟还是 RNN 模型,其词间距过长时还是会有梯度消失问题,根本原因在于用到了「递归」。当递归作用在同一个 weight matrix 上时,使得如果这个矩阵满足条件的话,其最大的特征值要是小于 1 的话,就一定出现梯度消失问题。后来的 LSTM 和 GRU 也仅仅能缓解问题,并不能根本解决。</li>\n <li>并行效果差:每个时刻的结果依赖前一时刻。</li>\n</ul>\n\n<h3 id=\"第-5-节--为什么说-rnn-模型没有体现注意力\">第 5 节 · 为什么说 RNN 模型没有体现「注意力」?</h3>\n\n<p>Encoder-Decoder 的一个非常严重的问题,是依赖中间那个 context 向量,则无法处理特别长的输入序列 —— 记忆力不足,会忘事儿。而忘事儿的根本原因,是没有「注意力」。</p>\n\n<p>对于一般的 RNN 模型,Encoder-Decoder 结构并没有体现「注意力」—— 这句话怎么理解?当输入序列经过 Encoder 生成的中间结果(上下文 C),被喂给 Decoder 时,这些中间结果对所生成序列里的哪个词,都没有区别(没有特别关照谁)。这相当于在说:输入序列里的每个词,对于生成任何一个输出的词的影响,是一样的,而不是输出某个词时是聚焦特定的一些输入词。这就是模型没有注意力机制。</p>\n\n<p>人脑的注意力模型,其实是资源分配模型。NLP 领域的注意力模型,是在 2014 年被提出的,后来逐渐成为 NLP 领域的一个广泛应用的机制。可以应用的场景,比如对于一个电商平台中很常见的白底图,其边缘的白色区域都是无用的,那么就不应该被关注(关注权重为 0)。比如机器翻译中,翻译词都是对局部输入重点关注的。</p>\n\n<p>所以 Attention 机制,就是在 Decoder 时,不是所有输出都依赖相同的「上下文 \\(\\bm{C}_t\\) 」,而是时刻 t 的输出,使用 \\(\\bm{C}_t\\) ,而这个 \\(\\bm{C}_t\\) 来自对每个输入数据项根据「注意力」进行的加权。</p>\n\n<h3 id=\"第-6-节--基于-attention-机制的-encoder-decoder-模型\">第 6 节 · 基于 Attention 机制的 Encoder-Decoder 模型</h3>\n\n<p>2015 年 Dzmitry Bahdanau 等人在论文<a href=\"https://arxiv.org/abs/1409.0473\">《Neural Machine Translation by Jointly Learning to Align and Translate》</a> 中提出了「Attention」机制,下面请跟着麦克船长,船长会深入浅出地为你解释清楚。</p>\n\n<p>下图中 \\(e_i\\) 表示编码器的隐藏层输出, \\(d_i\\) 表示解码器的隐藏层输出</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-f66c634a9c7c02915e5610af76c3b1b7\" width=\"436pt\" height=\"336pt\" viewBox=\"0.00 0.00 436.00 336.00\">\n<title>graphviz-f66c634a9c7c02915e5610af76c3b1b7</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\t{rank=same e1 e2 eddd en}\n\t{rank=same d1 d2 dddd dt0 dt dddd2}\n\n\teddd[label="..."]\n\tdddd[label="..."]\n\txddd[label="..."]\n\tyddd[label="..."]\n\tdt[label="d_t"]\n\tdt0[label="d_t-1"]\n\tyt[label="y_t"]\n\tyt0[label="y_t-1"]\n\tCt[shape=plaintext]\n\tx1[shape=plaintext]\n\tx2[shape=plaintext]\n\txddd[shape=plaintext]\n\txn[shape=plaintext]\n\ty1[shape=plaintext]\n\ty2[shape=plaintext]\n\tyddd[shape=plaintext]\n\tdddd2[shape=plaintext, label=""]\n\tCt[label="C_t", shape="square"]\n\n\tx1 -> e1\n\tx2 -> e2\n\txddd -> eddd\n\txn -> en\n\n\te1 -> e2\n\te2 -> eddd\n\teddd -> en\n\n\tCt -> dt\n\n\td1 -> y1\n\td2 -> y2\n\tdddd -> yddd\n\tdt0 -> yt0\n\tdt -> yt\n\n\td1 -> d2\n\td2 -> dddd\n\tdddd -> dt0\n\tdt0 -> dt\n\n\te1 -> Ct\n\te2 -> Ct\n\teddd -> Ct\n\ten -> Ct\n\n\tdt -> dddd2\n\tdt0 -> Ct\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 332)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-332 432,-332 432,4 -4,4\" />\n<!-- e1 -->\n<g id=\"node1\" class=\"node\">\n<title>e1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"181\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"181\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e1</text>\n</g>\n<!-- e2 -->\n<g id=\"node2\" class=\"node\">\n<title>e2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"253\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"253\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">e2</text>\n</g>\n<!-- e1->e2 -->\n<g id=\"edge5\" class=\"edge\">\n<title>e1->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.22,-90C208.22,-90 215.74,-90 215.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"215.74,-93.5 225.74,-90 215.74,-86.5 215.74,-93.5\" />\n</g>\n<!-- Ct -->\n<g id=\"node15\" class=\"node\">\n<title>Ct</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"309,-184 269,-184 269,-144 309,-144 309,-184\" />\n<text text-anchor=\"middle\" x=\"289\" y=\"-160.3\" font-family=\"Times,serif\" font-size=\"14.00\">C_t</text>\n</g>\n<!-- e1->Ct -->\n<g id=\"edge18\" class=\"edge\">\n<title>e1->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M203,-100.6C203,-121.06 203,-164 203,-164 203,-164 258.62,-164 258.62,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"258.62,-167.5 268.62,-164 258.62,-160.5 258.62,-167.5\" />\n</g>\n<!-- eddd -->\n<g id=\"node3\" class=\"node\">\n<title>eddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"325\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"325\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- e2->eddd -->\n<g id=\"edge6\" class=\"edge\">\n<title>e2->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M280.22,-90C280.22,-90 287.74,-90 287.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"287.74,-93.5 297.74,-90 287.74,-86.5 287.74,-93.5\" />\n</g>\n<!-- e2->Ct -->\n<g id=\"edge19\" class=\"edge\">\n<title>e2->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M274.5,-100.92C274.5,-100.92 274.5,-133.82 274.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"271,-133.82 274.5,-143.82 278,-133.82 271,-133.82\" />\n</g>\n<!-- en -->\n<g id=\"node4\" class=\"node\">\n<title>en</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"397\" cy=\"-90\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"397\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">en</text>\n</g>\n<!-- eddd->en -->\n<g id=\"edge7\" class=\"edge\">\n<title>eddd->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M352.22,-90C352.22,-90 359.74,-90 359.74,-90\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"359.74,-93.5 369.74,-90 359.74,-86.5 359.74,-93.5\" />\n</g>\n<!-- eddd->Ct -->\n<g id=\"edge20\" class=\"edge\">\n<title>eddd->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M303.5,-100.92C303.5,-100.92 303.5,-133.82 303.5,-133.82\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"300,-133.82 303.5,-143.82 307,-133.82 300,-133.82\" />\n</g>\n<!-- en->Ct -->\n<g id=\"edge21\" class=\"edge\">\n<title>en->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M399,-108.29C399,-130.21 399,-164 399,-164 399,-164 319.18,-164 319.18,-164\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"319.18,-160.5 309.18,-164 319.18,-167.5 319.18,-160.5\" />\n</g>\n<!-- d1 -->\n<g id=\"node5\" class=\"node\">\n<title>d1</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d1</text>\n</g>\n<!-- d2 -->\n<g id=\"node6\" class=\"node\">\n<title>d2</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"99\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d2</text>\n</g>\n<!-- d1->d2 -->\n<g id=\"edge14\" class=\"edge\">\n<title>d1->d2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.22,-238C54.22,-238 61.74,-238 61.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"61.74,-241.5 71.74,-238 61.74,-234.5 61.74,-241.5\" />\n</g>\n<!-- y1 -->\n<g id=\"node19\" class=\"node\">\n<title>y1</title>\n<text text-anchor=\"middle\" x=\"27\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y1</text>\n</g>\n<!-- d1->y1 -->\n<g id=\"edge9\" class=\"edge\">\n<title>d1->y1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M27,-256.17C27,-256.17 27,-281.59 27,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"23.5,-281.59 27,-291.59 30.5,-281.59 23.5,-281.59\" />\n</g>\n<!-- dddd -->\n<g id=\"node7\" class=\"node\">\n<title>dddd</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"171\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- d2->dddd -->\n<g id=\"edge15\" class=\"edge\">\n<title>d2->dddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M126.22,-238C126.22,-238 133.74,-238 133.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"133.74,-241.5 143.74,-238 133.74,-234.5 133.74,-241.5\" />\n</g>\n<!-- y2 -->\n<g id=\"node20\" class=\"node\">\n<title>y2</title>\n<text text-anchor=\"middle\" x=\"99\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y2</text>\n</g>\n<!-- d2->y2 -->\n<g id=\"edge10\" class=\"edge\">\n<title>d2->y2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M99,-256.17C99,-256.17 99,-281.59 99,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"95.5,-281.59 99,-291.59 102.5,-281.59 95.5,-281.59\" />\n</g>\n<!-- dt0 -->\n<g id=\"node8\" class=\"node\">\n<title>dt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-238\" rx=\"33.6\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t-1</text>\n</g>\n<!-- dddd->dt0 -->\n<g id=\"edge16\" class=\"edge\">\n<title>dddd->dt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M198.19,-238C198.19,-238 206.2,-238 206.2,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"206.2,-241.5 216.2,-238 206.2,-234.5 206.2,-241.5\" />\n</g>\n<!-- yddd -->\n<g id=\"node12\" class=\"node\">\n<title>yddd</title>\n<text text-anchor=\"middle\" x=\"171\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- dddd->yddd -->\n<g id=\"edge11\" class=\"edge\">\n<title>dddd->yddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M171,-256.17C171,-256.17 171,-281.59 171,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"167.5,-281.59 171,-291.59 174.5,-281.59 167.5,-281.59\" />\n</g>\n<!-- dt -->\n<g id=\"node9\" class=\"node\">\n<title>dt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-238\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-234.3\" font-family=\"Times,serif\" font-size=\"14.00\">d_t</text>\n</g>\n<!-- dt0->dt -->\n<g id=\"edge17\" class=\"edge\">\n<title>dt0->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M283.96,-238C283.96,-238 291.98,-238 291.98,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"291.98,-241.5 301.98,-238 291.98,-234.5 291.98,-241.5\" />\n</g>\n<!-- yt0 -->\n<g id=\"node14\" class=\"node\">\n<title>yt0</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"250\" cy=\"-310\" rx=\"33.29\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"250\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t-1</text>\n</g>\n<!-- dt0->yt0 -->\n<g id=\"edge12\" class=\"edge\">\n<title>dt0->yt0</title>\n<path fill=\"none\" stroke=\"black\" d=\"M250,-256.17C250,-256.17 250,-281.59 250,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-281.59 250,-291.59 253.5,-281.59 246.5,-281.59\" />\n</g>\n<!-- dt0->Ct -->\n<g id=\"edge23\" class=\"edge\">\n<title>dt0->Ct</title>\n<path fill=\"none\" stroke=\"black\" d=\"M276.4,-226.44C276.4,-226.44 276.4,-194.12 276.4,-194.12\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"279.9,-194.12 276.4,-184.12 272.9,-194.12 279.9,-194.12\" />\n</g>\n<!-- dddd2 -->\n<g id=\"node10\" class=\"node\">\n<title>dddd2</title>\n</g>\n<!-- dt->dddd2 -->\n<g id=\"edge22\" class=\"edge\">\n<title>dt->dddd2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M356.22,-238C356.22,-238 363.74,-238 363.74,-238\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"363.74,-241.5 373.74,-238 363.74,-234.5 363.74,-241.5\" />\n</g>\n<!-- yt -->\n<g id=\"node13\" class=\"node\">\n<title>yt</title>\n<ellipse fill=\"none\" stroke=\"black\" cx=\"329\" cy=\"-310\" rx=\"27\" ry=\"18\" />\n<text text-anchor=\"middle\" x=\"329\" y=\"-306.3\" font-family=\"Times,serif\" font-size=\"14.00\">y_t</text>\n</g>\n<!-- dt->yt -->\n<g id=\"edge13\" class=\"edge\">\n<title>dt->yt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M329,-256.17C329,-256.17 329,-281.59 329,-281.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"325.5,-281.59 329,-291.59 332.5,-281.59 325.5,-281.59\" />\n</g>\n<!-- xddd -->\n<g id=\"node11\" class=\"node\">\n<title>xddd</title>\n<text text-anchor=\"middle\" x=\"325\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">...</text>\n</g>\n<!-- xddd->eddd -->\n<g id=\"edge3\" class=\"edge\">\n<title>xddd->eddd</title>\n<path fill=\"none\" stroke=\"black\" d=\"M325,-36.17C325,-36.17 325,-61.59 325,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"321.5,-61.59 325,-71.59 328.5,-61.59 321.5,-61.59\" />\n</g>\n<!-- Ct->dt -->\n<g id=\"edge8\" class=\"edge\">\n<title>Ct->dt</title>\n<path fill=\"none\" stroke=\"black\" d=\"M305.5,-184.22C305.5,-184.22 305.5,-218.8 305.5,-218.8\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"302,-218.8 305.5,-228.8 309,-218.8 302,-218.8\" />\n</g>\n<!-- x1 -->\n<g id=\"node16\" class=\"node\">\n<title>x1</title>\n<text text-anchor=\"middle\" x=\"181\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x1</text>\n</g>\n<!-- x1->e1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>x1->e1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M181,-36.17C181,-36.17 181,-61.59 181,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"177.5,-61.59 181,-71.59 184.5,-61.59 177.5,-61.59\" />\n</g>\n<!-- x2 -->\n<g id=\"node17\" class=\"node\">\n<title>x2</title>\n<text text-anchor=\"middle\" x=\"253\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">x2</text>\n</g>\n<!-- x2->e2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>x2->e2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M253,-36.17C253,-36.17 253,-61.59 253,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"249.5,-61.59 253,-71.59 256.5,-61.59 249.5,-61.59\" />\n</g>\n<!-- xn -->\n<g id=\"node18\" class=\"node\">\n<title>xn</title>\n<text text-anchor=\"middle\" x=\"397\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">xn</text>\n</g>\n<!-- xn->en -->\n<g id=\"edge4\" class=\"edge\">\n<title>xn->en</title>\n<path fill=\"none\" stroke=\"black\" d=\"M397,-36.17C397,-36.17 397,-61.59 397,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"393.5,-61.59 397,-71.59 400.5,-61.59 393.5,-61.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>更进一步细化关于 \\(\\bm{C}_t\\) 部分,船长在此引用《基于深度学习的道路短期交通状态时空序列预测》一书中的图:</p>\n\n<p><img src=\"/img/src/2023-01-04-captain-nlp-5.png\" alt=\"image\" /></p>\n\n<p>这个图里的 \\(\\widetilde{h}_i\\) 与上一个图里的 \\(d_i\\) 对应, \\(h_i\\) 与上一个图里的 \\(e_i\\) 对应。</p>\n\n<p>针对时刻 \\(t\\) 要产出的输出,隐藏层每一个隐藏细胞都与 \\(\\bm{C}_t\\) 有一个权重关系 \\(\\alpha_{t,i}\\) 其中 \\(1\\le i\\le n\\) ,这个权重值与「输入项经过编码器后隐藏层后的输出 \\(e_i(1\\le i\\le n)\\) 、解码器的前一时刻隐藏层输出 \\(d_{t-1}\\) 」两者有关:</p>\n\n\\[\\begin{aligned}\n&s_{i,t} = score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n&\\alpha_{i,t} = \\frac{exp(s_{i,t})}{\\textstyle\\sum_{j=1}^n exp(s_{j,t})}\n\\end{aligned}\\]\n\n<p>常用的 \\(score\\) 函数有:</p>\n\n<ul>\n <li>点积(Dot Product)模型: \\(s_{i,t} = {\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i\\)</li>\n <li>缩放点积(Scaled Dot-Product)模型: \\(s_{i,t} = \\frac{{\\bm{d}_{t-1}}^T \\cdot \\bm{e}_i}{\\sqrt{\\smash[b]{dimensions\\:of\\:d_{t-1}\\:or\\:e_i}}}\\) ,可避免因为向量维度过大导致点积结果太大</li>\n</ul>\n\n<p>然后上下文向量就表示成:</p>\n\n\\[\\begin{aligned}\n&\\bm{C}_t = \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i\n\\end{aligned}\\]\n\n<p>还记得 RNN 那部分里船长讲到的 Encoder-Decoder 模型的公式表示吗?</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C} &= f_1(e_n) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C})\n\\end{aligned}\\]\n\n<p>加入 Attention 机制的 Encoder-Decoder 模型如下。</p>\n\n\\[\\begin{aligned}\ne_t &= Encoder_{LSTM/GRU}(x_t, e_{t-1}) \\\\\n\\bm{C}_t &= f_1(e_1,e_2...e_n,d_{t-1}) \\\\\nd_t &= f_2(d_{t-1}, \\bm{C}_t) \\\\\ny_t &= Decoder_{LSTM/GRU}(y_{t-1}, d_{t-1}, \\bm{C}_t)\n\\end{aligned}\\]\n\n<p>这种同时考虑 Encoder、Decoder 的 Attention,就叫做「Encoder-Decoder Attention」,也常被叫做「Vanilla Attention」。可以看到上面最核心的区别是第二个公式 \\(C_t\\) 。加入 Attention 后,对所有数据给予不同的注意力分布。具体地,比如我们用如下的函数来定义这个模型:</p>\n\n\\[\\begin{aligned}\n\\bm{e} &= tanh(\\bm{W}^{xe} \\cdot \\bm{x} + \\bm{b}^{xe}) \\\\\ns_{i,t} &= score(\\bm{e}_i,\\bm{d}_{t-1}) \\\\\n\\alpha_{i,t} &= \\frac{e^{s_{i,t}}}{\\textstyle\\sum_{j=1}^n e^{s_{j,t}}} \\\\\n\\bm{C}_t &= \\displaystyle\\sum_{i=1}^n \\alpha_{i,t} \\bm{e}_i \\\\\n\\bm{d}_t &= tanh(\\bm{W}^{dd} \\cdot \\bm{d}_{t-1} + \\bm{b}^{dd} +\n\t\t\t\t \\bm{W}^{yd} \\cdot \\bm{y}_{t-1} + \\bm{b}^{yd} +\n\t\t\t\t \\bm{W}^{cd} \\cdot \\bm{C}_t + \\bm{b}^{cd}) \\\\\n\\bm{y} &= Softmax(\\bm{W}^{dy} \\cdot \\bm{d} + \\bm{b}^{dy})\n\\end{aligned}\\]\n\n<p>到这里你能发现注意力机制的什么问题不?</p>\n\n<ul>\n <li>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</li>\n</ul>\n\n<h2 id=\"第二章--transformer-在-2017-年横空出世\">第二章 · Transformer 在 2017 年横空出世</h2>\n\n<p>船长先通过一个动画来看下 Transformer 是举例示意,该图来自 Google 的博客文章 <a href=\"https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html\">《Transformer: A Novel Neural Network Architecture for Language Understanding》</a>:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-11.gif\" alt=\"image\" /></p>\n\n<p>中文网络里找到的解释得比较好的 blogs、answers,几乎都指向了同一篇博客:Jay Alammar 的<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,所以建议读者搭配该篇文章阅读。</p>\n\n<p>Transformer 模型中用到了自注意力(Self-Attention)、多头注意力(Multiple-Head Attention)、残差网络(ResNet)与捷径(Short-Cut)。下面我们先通过第 1 到第 4 小节把几个基本概念讲清楚,然后在第 5 小节讲解整体 Transformer 模型就会好理解很多了。最后第 6 小节我们来一段动手实践。</p>\n\n<h3 id=\"第-7-节--自注意力机制self-attention\">第 7 节 · 自注意力机制(Self-Attention)</h3>\n\n<p>自注意力是理解 Transformer 的关键,原作者在论文中限于篇幅,没有给出过多的解释。以下是我自己的理解,能够比较通透、符合常识地去理解 Transformer 中的一些神来之笔的概念。</p>\n\n<h4 id=\"71一段自然语言内容其自身就暗含很多内部关联信息\">7.1、一段自然语言内容,其自身就「暗含」很多内部关联信息</h4>\n\n<p>在加入了 Attention 的 Encoder-Decoder 模型中,对输出序列 Y 中的一个词的注意力来自于输入序列 X,那么如果 X 和 Y 相等呢?什么场景会有这个需求?因为我们认为一段文字里某些词就是由于另外某些词而决定的,可以粗暴地理解为「完形填空」的原理。那么这样一段文字,其实就存在其中每个词的自注意力,举个例子:</p>\n\n<blockquote>\n <p>老王是我的主管,我很喜欢他的平易近人。</p>\n</blockquote>\n\n<p>对这句话里的「他」,如果基于这句话计算自注意力的话,显然应该给予「老王」最多的注意力。受此启发,我们认为:</p>\n\n<blockquote>\n <p>一段自然语言中,其实暗含了:为了得到关于某方面信息 Q,可以通过关注某些信息 K,进而得到某些信息(V)作为结果。</p>\n</blockquote>\n\n<p>Q 就是 query 检索/查询,K、V 分别是 key、value。所以类似于我们在图书检索系统里搜索「NLP书籍」(这是 Q),得到了一本叫《自然语言处理实战》的电子书,书名就是 key,这本电子书就是 value。只是对于自然语言的理解,我们认为任何一段内容里,都自身暗含了很多潜在 Q-K-V 的关联。这是整体受到信息检索领域里 query-key-value 的启发的。</p>\n\n<p>基于这个启发,我们将自注意力的公式表示为:</p>\n\n\\[\\begin{aligned}\nZ = SelfAttention(X) = Attention(Q,K,V)\n\\end{aligned}\\]\n\n<p>X 经过自注意力计算后,得到的「暗含」了大量原数据内部信息的 Z。然后我们拿着这个带有自注意力信息的 Z 进行后续的操作。这里要强调的是,Z 向量中的每个元素 z_i 都与 X 的所有元素有某种关联,而不是只与 x_i 有关联。</p>\n\n<h4 id=\"72如何计算-qkv\">7.2、如何计算 Q、K、V</h4>\n\n<p>Q、K、V 全部来自输入 X 的线性变换:</p>\n\n\\[\\begin{aligned}\nQ &= W^Q \\cdot X \\\\\nK &= W^K \\cdot X \\\\\nV &= W^V \\cdot X\n\\end{aligned}\\]\n\n<p>\\(W^Q、W^K、W^V\\) 以随机初始化开始,经过训练就会得到非常好的表现。对于 \\(X\\) 中的每一个词向量 \\(x_i\\) ,经过这个变换后得到了:</p>\n\n\\[\\begin{aligned}\nq_i &= W^Q \\cdot x_i \\\\\nk_i &= W^K \\cdot x_i \\\\\nv_i &= W^V \\cdot x_i\n\\end{aligned}\\]\n\n<h4 id=\"73注意力函数如何通过-qv-得到-z\">7.3、注意力函数:如何通过 Q、V 得到 Z</h4>\n\n<p>基于上面的启发,我们认为 X 经过自注意力的挖掘后,得到了:</p>\n\n<ul>\n <li>暗含信息 1:一组 query 与一组 key 之间的关联,记作 qk(想一下信息检索系统要用 query 先招到 key)</li>\n <li>暗含信息 2:一组 value</li>\n <li>暗含信息 3:qk 与 value 的某种关联</li>\n</ul>\n\n<p>这三组信息,分别如何表示呢?这里又需要一些启发了,因为计算机科学其实是在「模拟还原」现实世界,在 AI 的领域目前的研究方向就是模拟还原人脑的思考。所以这种「模拟还原」都是寻找某一种近似方法,因此不能按照数学、物理的逻辑推理来理解,而应该按照「工程」或者「计算科学」来理解,想想我们大学时学的「计算方法」这门课,因此常需要一些启发来找到某种「表示」。</p>\n\n<p>这里 Transformer 的作者,认为 \\(Q\\) 和 \\(K\\) 两个向量之间的关联,是我们在用 \\(Q\\) 找其在 \\(K\\) 上的投影,如果 \\(Q\\) 、 \\(K\\) 是单位长度的向量,那么这个投影其实可以理解为找「 \\(Q\\) 和 \\(K\\) 向量之间的相似度」:</p>\n\n<ul>\n <li>如果 \\(Q\\) 和 \\(K\\) 垂直,那么两个向量正交,其点积(Dot Product)为 0;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 平行,那么两个向量点积为两者模积 \\(\\|Q\\|\\|K\\|\\) ;</li>\n <li>如果 \\(Q\\) 和 \\(K\\) 呈某个夹角,则点积就是 \\(Q\\) 在 \\(K\\) 上的投影的模。</li>\n</ul>\n\n<p>因此「暗含信息 1」就可以用「 \\(Q\\cdot K\\) 」再经过 Softmax 归一化来表示。这个表示,是一个所有元素都是 0~1 的矩阵,可以理解成对应注意力机制里的「注意力分数」,也就是一个「注意力分数矩阵(Attention Score Matrix)」。</p>\n\n<p>而「暗含信息 2」则是输入 \\(X\\) 经过的线性变换后的特征,看做 \\(X\\) 的另一种表示。然后我们用这个「注意力分数矩阵」来加持一下 \\(V\\) ,这个点积过程就表示了「暗含信息 3」了。所以我们有了如下公式:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(Q \\cdot K^T) \\cdot V\n\\end{aligned}\\]\n\n<p>其实到这里,这个注意力函数已经可以用了。有时候,为了避免因为向量维度过大,导致 \\(Q \\cdot K^T\\) 点积结果过大,我们再加一步处理:</p>\n\n\\[\\begin{aligned}\nZ = Attention(Q,K,V) = Softmax(\\frac{Q \\cdot K^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V\n\\end{aligned}\\]\n\n<p>这里 \\(d_k\\) 是 K 矩阵中向量 \\(k_i\\) 的维度。这一步修正还有进一步的解释,即如果经过 Softmax 归一化后模型稳定性存在问题。怎么理解?如果假设 Q 和 K 中的每个向量的每一维数据都具有零均值、单位方差,这样输入数据是具有稳定性的,那么如何让「暗含信息 1」计算后仍然具有稳定性呢?即运算结果依然保持零均值、单位方差,就是除以「 \\(\\sqrt{\\smash[b]{d_k}}\\) 」。</p>\n\n<p>到这里我们注意到:</p>\n\n<ul>\n <li>K、V 里的每一个向量,都是</li>\n</ul>\n\n<h4 id=\"74其他注意力函数\">7.4、其他注意力函数</h4>\n\n<p>为了提醒大家这种暗含信息的表示,都只是计算方法上的一种选择,好坏全靠结果评定,所以包括上面的在内,常见的注意力函数有(甚至你也可以自己定义):</p>\n\n\\[Z = Attention(Q,K,V) =\n\\begin{cases}\n\\begin{aligned}\n&= Softmax(Q^T K) V \\\\\n&= Softmax(\\frac{Q K^T}{\\sqrt{\\smash[b]{d_k}}}) V \\\\\n&= Softmax(\\omega^T tanh(W[q;k])) V \\\\\n&= Softmax(Q^T W K) V \\\\\n&= cosine[Q^T K] V\n\\end{aligned}\n\\end{cases}\\]\n\n<p>到这里,我们就从原始的输入 \\(X\\) 得到了一个包含自注意力信息的 \\(Z\\) 了,后续就可以用 \\(Z\\) 了。</p>\n\n<h3 id=\"第-8-节--多头注意力\">第 8 节 · 多头注意力</h3>\n\n<p>到这里我们理解了「自注意力」,而 Transformer 这篇论文通过添加「多头」注意力的机制进一步提升了注意力层。我们先看下它是什么,然后看下它的优点。从本小节开始,本文大量插图引用自<a href=\"http://jalammar.github.io/illustrated-transformer/\">《The Illustrated Transformer》</a>,作者 Jay Alammar 写出一篇非常深入浅出的图解文章,被大量引用,非常出色,再次建议大家去阅读。</p>\n\n<p>Transformer 中用了 8 个头,也就是 8 组不同的 Q-K-V:</p>\n\n\\[\\begin{aligned}\nQ_0 = W_0^Q \\cdot X ;\\enspace K_0 = &W_0^K \\cdot X ;\\enspace V_0 = W_0^V \\cdot X \\\\\nQ_1 = W_1^Q \\cdot X ;\\enspace K_1 = &W_0^K \\cdot X ;\\enspace V_1 = W_1^V \\cdot X \\\\\n&.... \\\\\nQ_7 = W_7^Q \\cdot X ;\\enspace K_7 = &W_0^K \\cdot X ;\\enspace V_7 = W_7^V \\cdot X\n\\end{aligned}\\]\n\n<p>这样我们就能得到 8 个 Z:</p>\n\n\\[\\begin{aligned}\n&Z_0 = Attention(Q_0,K_0,V_0) = Softmax(\\frac{Q_0 \\cdot K_0^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_0 \\\\\n&Z_1 = Attention(Q_1,K_1,V_1) = Softmax(\\frac{Q_1 \\cdot K_1^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_1 \\\\\n&... \\\\\n&Z_7 = Attention(Q_7,K_7,V_7) = Softmax(\\frac{Q_7 \\cdot K_7^T}{\\sqrt{\\smash[b]{d_k}}}) \\cdot V_7 \\\\\n\\end{aligned}\\]\n\n<p>然后我们把 \\(Z_0\\) 到 \\(Z_7\\) 沿着行数不变的方向全部连接起来,如下图所示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-3.png\" alt=\"image\" width=\"464\" /></p>\n\n<p>我们再训练一个权重矩阵 \\(W^O\\) ,然后用上面拼接的 \\(Z_{0-7}\\) 乘以这个权重矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-4.png\" alt=\"image\" width=\"135\" /></p>\n\n<p>于是我们会得到一个 Z 矩阵:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-5.png\" alt=\"image\" width=\"100\" /></p>\n\n<p>到这里就是多头注意力机制的全部内容,与单头注意力相比,都是为了得到一个 Z 矩阵,但是多头用了多组 Q-K-V,然后经过拼接、乘以权重矩阵得到最后的 Z。我们总览一下整个过程:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-6.png\" alt=\"image\" width=\"935\" /></p>\n\n<p>通过多头注意力,每个头都会关注到不同的信息,可以如下类似表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-7.png\" alt=\"image\" width=\"400\" /></p>\n\n<p>这通过两种方式提高了注意力层的性能:</p>\n\n<ul>\n <li>多头注意力机制,扩展了模型关注不同位置的能力。 \\(Z\\) 矩阵中的每个向量 \\(z_i\\) 包含了与 \\(X\\) 中所有向量 \\(x_i\\) 有关的一点编码信息。反过来说,不要认为 \\(z_i\\) 只与 \\(x_i\\) 有关。</li>\n <li>多头注意力机制,为注意力层提供了多个「表示子空间 Q-K-V」,以及 Z。这样一个输入矩阵 \\(X\\) ,就会被表示成 8 种不同的矩阵 Z,都包含了原始数据信息的某种解读暗含其中。</li>\n</ul>\n\n<h3 id=\"第-9-节--退化现象残差网络与-short-cut\">第 9 节 · 退化现象、残差网络与 Short-Cut</h3>\n\n<h4 id=\"91退化现象\">9.1、退化现象</h4>\n\n<p>对于一个 56 层的神经网路,我们很自然地会觉得应该比 20 层的神经网络的效果要好,比如说从误差率(error)的量化角度看。但是华人学者何凯明等人的论文<a href=\"https://arxiv.org/pdf/1512.03385.pdf\">《Deep Residual Learning for Image Recognition》</a>中给我们呈现了相反的结果,而这个问题的原因并不是因为层数多带来的梯度爆炸/梯度消失(毕竟已经用了归一化解决了这个问题),而是因为一种反常的现象,这种现象我们称之为「退化现象」。何凯明等人认为这是因为存在「难以优化好的网络层」。</p>\n\n<h4 id=\"92恒等映射\">9.2、恒等映射</h4>\n\n<p>如果这 36 层还帮了倒忙,那还不如没有,是不是?所以这多出来的 36 个网络层,如果对于提升性能(例如误差率)毫无影响,甚至更进一步,这 36 层前的输入数据,和经过这 36 层后的输出数据,完全相同,那么如果将这 36 层抽象成一个函数 \\(f_{36}\\) ,这就是一个恒等映射的函数:</p>\n\n\\[f_{36}(x) = x\\]\n\n<p>回到实际应用中。如果我们对于一个神经网络中的连续 N 层是提升性能,还是降低性能,是未知的,那么则可以建立一个跳过这些层的连接,实现:</p>\n\n<blockquote>\n <p>如果这 N 层可以提升性能,则采用这 N 层;否则就跳过。</p>\n</blockquote>\n\n<p>这就像给了这 N 层神经网络一个试错的空间,待我们确认它们的性能后再决定是否采用它们。同时也可以理解成,这些层可以去单独优化,如果性能提升,则不被跳过。</p>\n\n<h4 id=\"93残差网络residual-network与捷径short-cut\">9.3、残差网络(Residual Network)与捷径(Short-Cut)</h4>\n\n<p>如果前面 20 层已经可以实现 99% 的准确率,那么引入了这 36 层能否再提升「残差剩余那 1%」的准确率从而达到 100% 呢?所以这 36 层的网络,就被称为「残差网络(Residual Network,常简称为 ResNet)」,这个叫法非常形象。</p>\n\n<p>而那个可以跳过 N 层残差网络的捷径,则常被称为 Short-Cut,也会被叫做跳跃链接(Skip Conntection),这就解决了上述深度学习中的「退化现象」。</p>\n\n<h3 id=\"第-10-节--transformer-的位置编码positional-embedding\">第 10 节 · Transformer 的位置编码(Positional Embedding)</h3>\n\n<p>还记得我在第二部分最后提到的吗:</p>\n\n<blockquote>\n <p>这个注意力机制忽略了位置信息。比如 Tigers love rabbits 和 Rabbits love tigers 会产生一样的注意力分数。</p>\n</blockquote>\n\n<h4 id=\"101transformer-论文中的三角式位置编码sinusoidal-positional-encoding\">10.1、Transformer 论文中的三角式位置编码(Sinusoidal Positional Encoding)</h4>\n\n<p>现在我们来解决这个问题,为每一个输入向量 \\(x_i\\) 生成一个位置编码向量 \\(t_i\\) ,这个位置编码向量的维度,与输入向量(词的嵌入式向量表示)的维度是相同的:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-8.png\" alt=\"image\" width=\"500\" /></p>\n\n<p>Transformer 论文中给出了如下的公式,来计算位置编码向量的每一位的值:</p>\n\n\\[\\begin{aligned}\nP_{pos,2i} &= sin(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}}) \\\\\nP_{pos,2i+1} &= cos(\\frac{pos}{10000^{\\frac{2i}{d_{model}}}})\n\\end{aligned}\\]\n\n<p>这样对于一个 embedding,如果它在输入内容中的位置是 pos,那么其编码向量就表示为:</p>\n\n\\[\\begin{aligned}\n[P_{pos,0}, P_{pos,1}, ... , P_{pos,d_x-1}]\n\\end{aligned}\\]\n\n<p>延展开的话,位置编码其实还分为绝对位置编码(Absolute Positional Encoding)、相对位置编码(Relative Positional Encoding)。前者是专门生成位置编码,并想办法融入到输入中,我们上面看到的就是一种。后者是微调 Attention 结构,使得它可以分辨不同位置的数据。另外其实还有一些无法分类到这两种的位置编码方法。</p>\n\n<h4 id=\"102绝对位置编码\">10.2、绝对位置编码</h4>\n\n<p>绝对位置编码,如上面提到的,就是定义一个位置编码向量 \\(t_i\\) ,通过 \\(x_i + t_i\\) 就得到了一个含有位置信息的向量。</p>\n\n<ul>\n <li>习得式位置编码(Learned Positional Encoding):将位置编码当做训练参数,生成一个「最大长度 x 编码维度」的位置编码矩阵,随着训练进行更新。目前 Google BERT、OpenAI GPT 模型都是用的这种位置编码。缺点是「外推性」差,如果文本长度超过之前训练时用的「最大长度」则无法处理。目前有一些给出优化方案的论文,比如「<a href=\"https://mp.weixin.qq.com/s?__biz=MzIwMTc4ODE0Mw==&mid=2247515573&idx=1&sn=2d719108244ada7db3a535a435631210&chksm=96ea6235a19deb23babde5eaac484d69e4c2f53bab72d2e350f75bed18323eea3cf9be30615b#rd\">层次分解位置编码</a>」。</li>\n <li>三角式位置编码(Sinusoidal Positional Encodign):上面提过了。</li>\n <li>循环式位置编码(Recurrent Positional Encoding):通过一个 RNN 再接一个 Transformer,那么 RNN 暗含的「顺序」就导致不再需要额外编码了。但这样牺牲了并行性,毕竟 RNN 的两大缺点之一就有这个。</li>\n <li>相乘式位置编码(Product Positional Encoding):用「 \\(x_i \\odot t_i\\) 」代替「 \\(x_i + t_i\\) 」。</li>\n</ul>\n\n<h4 id=\"103相对位置编码和其他位置编码\">10.3、相对位置编码和其他位置编码</h4>\n\n<p>最早来自于 Google 的论文<a href=\"https://arxiv.org/abs/1803.02155\">《Self-Attention with Relative Position Representations》</a>相对位置编码,考虑的是当前 position 与被 attention 的 position 之前的相对位置。</p>\n\n<ul>\n <li>常见相对位置编码:经典式、XLNET 式、T5 式、DeBERTa 式等。</li>\n <li>其他位置编码:CNN 式、复数式、融合式等。</li>\n</ul>\n\n<p>到此我们都是在讲 Encoder,目前我们知道一个 Encoder 可以用如下的示意图表示:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-12.png\" alt=\"image\" width=\"680\" /></p>\n\n<h3 id=\"第-11-节--transformer-的编码器-encoder-和解码器-decoder\">第 11 节 · Transformer 的编码器 Encoder 和解码器 Decoder</h3>\n\n<h4 id=\"111encoder-和-decoder-的图示结构\">11.1、Encoder 和 Decoder 的图示结构</h4>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-15.png\" alt=\"image\" width=\"165\" /></p>\n\n<ul>\n <li>第一层是多头注意力层(Multi-Head Attention Layer)。</li>\n <li>第二层是经过一个前馈神经网络(Feed Forward Neural Network,简称 FFNN)。</li>\n <li>这两层,每一层都有「Add & Normalization」和 ResNet。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-14.png\" alt=\"image\" width=\"179\" /></p>\n\n<ul>\n <li>解码器有两个多头注意力层。第一个多头注意力层是 Masked Multi-Head Attention 层,即在自注意力计算的过程中只有前面位置上的内容。第二个多头注意力层买有被 Masked,是个正常多头注意力层。</li>\n <li>可以看出来,第一个注意力层是一个自注意力层(Self Attention Layer),第二个是 Encoder-Decoder Attention 层(它的 K、V 来自 Encoder,Q 来自自注意力层),有些文章里会用这个角度来指代。</li>\n <li>FNN、Add & Norm、ResNet 都与 Encoder 类似。</li>\n</ul>\n\n<h4 id=\"112decoder-的第一个输出结果\">11.2、Decoder 的第一个输出结果</h4>\n\n<p>产出第一个最终输出结果的过程:</p>\n\n<ul>\n <li>不需要经过 Masked Multi-Head Attention Layer(自注意力层)。</li>\n <li>只经过 Encoder-Decoder Attention Layer。</li>\n</ul>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-13.png\" alt=\"image\" width=\"695\" /></p>\n\n<p>这样我们就像前面的 Encoder-Decoder Attention 模型一样,得到第一个输出。但是最终的输出结果,还会经过一层「Linear + Softmax」。</p>\n\n<h4 id=\"113decoder-后续的所有输出\">11.3、Decoder 后续的所有输出</h4>\n\n<p>从产出第二个输出结果开始:</p>\n\n<ul>\n <li>Decoder 的自注意力层,会用到前面的输出结果。</li>\n <li>可以看到,这是一个串行过程。</li>\n</ul>\n\n<h4 id=\"114decoder-之后的-linear-和-softmax\">11.4、Decoder 之后的 Linear 和 Softmax</h4>\n\n<p>经过所有 Decoder 之后,我们得到了一大堆浮点数的结果。最后的 Linear & Softmax 就是来解决「怎么把它变成文本」的问题的。</p>\n\n<ul>\n <li>Linear 是一个全连接神经网络,把 Decoders 输出的结果投影到一个超大的向量上,我们称之为 logits 向量。</li>\n <li>如果我们的输出词汇表有 1 万个词,那么 logits 向量的每一个维度就有 1 万个单元,每个单元都对应输出词汇表的一个词的概率。</li>\n <li>Softmax 将 logits 向量中的每一个维度都做归一化,这样每个维度都能从 1 万个单元对应的词概率中选出最大的,对应的词汇表里的词,就是输出词。最终得到一个输出字符串。</li>\n</ul>\n\n<h3 id=\"第-12-节--transformer-模型整体\">第 12 节 · Transformer 模型整体</h3>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-16.png\" alt=\"image\" width=\"660\" /></p>\n\n<p>最后我们再来整体看一下 Transformer:</p>\n\n<ul>\n <li>首先输入数据生成词的嵌入式向量表示(Embedding),生成位置编码(Positional Encoding,简称 PE)。</li>\n <li>进入 Encoders 部分。先进入多头注意力层(Multi-Head Attention),是自注意力处理,然后进入全连接层(又叫前馈神经网络层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Encoder 的输入,都来自前一个 Encoder 的输出,但是第一个 Encoder 的输入就是 Embedding + PE。</li>\n <li>进入 Decoders 部分。先进入第一个多头注意力层(是 Masked 自注意力层),再进入第二个多头注意力层(是 Encoder-Decoder 注意力层),每层都有 ResNet、Add & Norm。</li>\n <li>每一个 Decoder 都有两部分输入。</li>\n <li>Decoder 的第一层(Maksed 多头自注意力层)的输入,都来自前一个 Decoder 的输出,但是第一个 Decoder 是不经过第一层的(因为经过算出来也是 0)。</li>\n <li>Decoder 的第二层(Encoder-Decoder 注意力层)的输入,Q 都来自该 Decoder 的第一层,且每个 Decoder 的这一层的 K、V 都是一样的,均来自最后一个 Encoder。</li>\n <li>最后经过 Linear、Softmax 归一化。</li>\n</ul>\n\n<h3 id=\"第-13-节--transformer-的性能\">第 13 节 · Transformer 的性能</h3>\n\n<p>Google 在其博客于 2017.08.31 发布如下测试数据:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2023-01-04-language-model-5-9.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2023-01-04-language-model-5-10.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<h2 id=\"第三章--一个基于-tensorflow-架构的-transformer-实现\">第三章 · 一个基于 TensorFlow 架构的 Transformer 实现</h2>\n\n<p>我们来看看一个简单的 Transformer 模型,就是比较早出现的 Kyubyong 实现的 Transformer 模型:https://github.com/Kyubyong/transformer/tree/master/tf1.2_legacy</p>\n\n<h3 id=\"第-14-节--先训练和测试一下-kyubyong-transformer\">第 14 节 · 先训练和测试一下 Kyubyong Transformer</h3>\n\n<p>下载一个「德语-英语翻译」的数据集:https://drive.google.com/uc?id=1l5y6Giag9aRPwGtuZHswh3w5v3qEz8D8</p>\n\n<p>把 <code class=\"language-plaintext highlighter-rouge\">de-en</code> 下面的 <code class=\"language-plaintext highlighter-rouge\">tgz</code> 解压后放在 <code class=\"language-plaintext highlighter-rouge\">corpora/</code> 目录下。如果需要先修改超参数,需要修改 <code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code>。然后运行如下命令,生成词汇文件(vocabulary files),默认到 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 目录下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python prepro.py\n</code></pre></div></div>\n\n<p>然后开始训练:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python train.py\n</code></pre></div></div>\n\n<p>也可以跳过训练,直接<a href=\"https://www.dropbox.com/s/fo5wqgnbmvalwwq/logdir.zip?dl=0\">下载预训练过的文件</a>,是一个 <code class=\"language-plaintext highlighter-rouge\">logdir/</code> 目录,把它放到项目根目录下。然后可以对训练出来的结果,运行评价程序啦:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python eval.py\n</code></pre></div></div>\n\n<p>会生成「德语-英语」测试结果文件在 <code class=\"language-plaintext highlighter-rouge\">results/</code> 目录下,内容如下:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>- source: Sie war eine jährige Frau namens Alex\n- expected: She was a yearold woman named Alex\n- got: She was a <UNK> of vote called <UNK>\n\n- source: Und als ich das hörte war ich erleichtert\n- expected: Now when I heard this I was so relieved\n- got: And when I was I <UNK> 's\n\n- source: Meine Kommilitonin bekam nämlich einen Brandstifter als ersten Patienten\n- expected: My classmate got an arsonist for her first client\n- got: Because my first eye was a first show\n\n- source: Das kriege ich hin dachte ich mir\n- expected: This I thought I could handle\n- got: I would give it to me a day\n\n- source: Aber ich habe es nicht hingekriegt\n- expected: But I didn't handle it\n- got: But I didn't <UNK> <UNK>\n\n- source: Ich hielt dagegen\n- expected: I pushed back\n- got: I <UNK>\n\n...\n\nBleu Score = 6.598452846670836\n</code></pre></div></div>\n\n<p>评估结果文件的最后一行是 Bleu Score:</p>\n\n<ul>\n <li>这是用来评估机器翻译质量的一种度量方式。它是由几个不同的 BLEU 分数组成的,每个 BLEU 分数都表示翻译结果中与参考翻译的重叠程度。</li>\n <li>一个常用的 BLEU 分数是 BLEU-4,它计算翻译结果中与参考翻译的 N 元文法语言模型 n-gram(n 为 4)的重叠程度。分数越高表示翻译结果越接近参考翻译。</li>\n</ul>\n\n<h3 id=\"第-15-节--kyubyong-transformer-源码分析\">第 15 节 · Kyubyong Transformer 源码分析</h3>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">hparams.py</code>:超参数都在这里,仅 30 行。将在下面 <code class=\"language-plaintext highlighter-rouge\">2.1</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:装载、批处理数据的相关函数,代码仅 92 行。主要在下面 <code class=\"language-plaintext highlighter-rouge\">2.2</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">prepro.py</code>:为 source 和 target 创建词汇文件(vocabulary file),代码仅 39 行。下面 <code class=\"language-plaintext highlighter-rouge\">2.3</code> 部分会为大家解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:代码仅 184 行。在下面 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">modules.py</code>:Encoding / Decoding 网络的构建模块,代码仅 329 行。与 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 一起会在 <code class=\"language-plaintext highlighter-rouge\">2.4</code> 部分解读。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:评估效果,代码仅 82 行。将在 <code class=\"language-plaintext highlighter-rouge\">2.5</code> 部分解读</li>\n</ul>\n\n<p>总计 700 多行代码。</p>\n\n<h4 id=\"151超参数\">15.1、超参数</h4>\n\n<p><code class=\"language-plaintext highlighter-rouge\">hyperparams.py</code> 文件中定义了 <code class=\"language-plaintext highlighter-rouge\">Hyperparams</code> 超参数类,其中包含的参数我们逐一来解释一下:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">source_train</code>:训练数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.de'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_train</code>:训练数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/train.tags.de-en.en'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">source_test</code>:测试数据集的源输入文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.de.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">target_test</code>:测试数据集的目标输出文件,默认是 <code class=\"language-plaintext highlighter-rouge\">'corpora/IWSLT16.TED.tst2014.de-en.en.xml'</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">batch_size</code>:设置每批数据的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">lr</code>:设置学习率 learning rate。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">logdir</code>:设置日志文件保存的目录。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">maxlen</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">min_cnt</code></li>\n <li><code class=\"language-plaintext highlighter-rouge\">hidden_units</code>:设置编码器和解码器中隐藏层单元的数量。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_blocks</code>:编码器(encoder block)、解码器(decoder block)的数量</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code>:训练过程中迭代的次数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_heads</code>:还记得上面文章里我们提到的 Transformer 中用到了多头注意力吧,这里就是多头注意力的头数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">droupout_rate</code>:设置 dropout 层的 dropout rate,具体 dropout 请看 2.4.1 部分。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">sinusoid</code>:设置为 <code class=\"language-plaintext highlighter-rouge\">True</code> 时表示使用正弦函数计算位置编码,否则为 <code class=\"language-plaintext highlighter-rouge\">False</code> 时表示直接用 <code class=\"language-plaintext highlighter-rouge\">position</code> 做位置编码。</li>\n</ul>\n\n<h4 id=\"152预处理\">15.2、预处理</h4>\n\n<p>文件 <code class=\"language-plaintext highlighter-rouge\">prepro.py</code> 实现了预处理的过程,根据 <code class=\"language-plaintext highlighter-rouge\">hp.source_train</code> 和 <code class=\"language-plaintext highlighter-rouge\">hp.target_train</code> 分别创建 <code class=\"language-plaintext highlighter-rouge\">\"de.vocab.tsv\"</code> 和 <code class=\"language-plaintext highlighter-rouge\">\"en.vocab.tsv\"</code> 两个词汇表。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"n\">fname</span><span class=\"p\">):</span>\n\n <span class=\"c1\"># 使用 codecs.open 函数读取指定文件路径(fpath)的文本内容,并将其存储在 text 变量中\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">fpath</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 将 text 中的非字母和空格的字符去掉\n</span> <span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将 text 中的文本按照空格分割,并将每个单词存储在 words 变量中\n</span> <span class=\"n\">words</span> <span class=\"o\">=</span> <span class=\"n\">text</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># words 中每个单词的词频\n</span> <span class=\"n\">word2cnt</span> <span class=\"o\">=</span> <span class=\"n\">Counter</span><span class=\"p\">(</span><span class=\"n\">words</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 检查是否存在 preprocessed 文件夹,如果不存在就创建\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'preprocessed'</span><span class=\"p\">)</span>\n <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/{}'</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">fname</span><span class=\"p\">),</span> <span class=\"s\">'w'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 按出现次数从多到少的顺序写入每个单词和它的出现次数\n</span> \t<span class=\"c1\"># 在文件最前面写入四个特殊字符 <PAD>, <UNK>, <S>, </S> 分别用于填充,未知单词,句子开始和句子结束\n</span> <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">{}</span><span class=\"se\">\\t</span><span class=\"s\">1000000000</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"s\">\"<PAD>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<UNK>\"</span><span class=\"p\">,</span> <span class=\"s\">\"<S>\"</span><span class=\"p\">,</span> <span class=\"s\">\"</S>\"</span><span class=\"p\">))</span>\n <span class=\"k\">for</span> <span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span> <span class=\"ow\">in</span> <span class=\"n\">word2cnt</span><span class=\"p\">.</span><span class=\"n\">most_common</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">word2cnt</span><span class=\"p\">)):</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"sa\">u</span><span class=\"s\">\"{}</span><span class=\"se\">\\t</span><span class=\"s\">{}</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"n\">cnt</span><span class=\"p\">))</span>\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">\"de.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"n\">make_vocab</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">\"en.vocab.tsv\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>在主函数中调用 make_vocab 函数,在目录 <code class=\"language-plaintext highlighter-rouge\">preprocessed</code> 生成 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个词汇表文件。</li>\n <li>在函数 <code class=\"language-plaintext highlighter-rouge\">make_vocab</code> 中,先使用 <code class=\"language-plaintext highlighter-rouge\">codecs.open</code> 函数读取指定文件路径 <code class=\"language-plaintext highlighter-rouge\">fpath</code> 的文本内容,并将其存储在 <code class=\"language-plaintext highlighter-rouge\">text</code> 变量中,再使用正则表达式 <code class=\"language-plaintext highlighter-rouge\">regex</code> 将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的非字母和空格的字符去掉,接着将 <code class=\"language-plaintext highlighter-rouge\">text</code> 中的文本按照空格分割,并将每个单词存储在 <code class=\"language-plaintext highlighter-rouge\">words</code> 变量中。</li>\n <li>接下来,使用 <code class=\"language-plaintext highlighter-rouge\">Counter</code> 函数统计 <code class=\"language-plaintext highlighter-rouge\">words</code> 中每个单词的出现次数,并将统计结果存储在 <code class=\"language-plaintext highlighter-rouge\">word2cnt</code> 变量中。</li>\n <li>最后所有词与词频,存储在 <code class=\"language-plaintext highlighter-rouge\">de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">en.vocab.tsv</code> 两个文件中。</li>\n</ul>\n\n<h4 id=\"153训练测试数据集的加载\">15.3、训练/测试数据集的加载</h4>\n\n<p>我们先看下 <code class=\"language-plaintext highlighter-rouge\">train.py</code>、<code class=\"language-plaintext highlighter-rouge\">data_load.py</code>、<code class=\"language-plaintext highlighter-rouge\">eval.py</code> 三个文件:</p>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">train.py</code>:该文件包含了 <code class=\"language-plaintext highlighter-rouge\">Graph</code> 类的定义,并在其构造函数中调用 <code class=\"language-plaintext highlighter-rouge\">load_data.py</code> 文件中的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 函数加载训练数据。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">data_load.py</code>:定义了加载训练数据、加载测试数据的函数。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">eval.py</code>:测试结果的评价函数定义在这个文件里。</li>\n</ul>\n\n<p>下面是函数调用的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-9559986008ed2e1e47e8729260efda61\" width=\"830pt\" height=\"98pt\" viewBox=\"0.00 0.00 830.00 98.00\">\n<title>graphviz-9559986008ed2e1e47e8729260efda61</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\t训练 -> Graph构造函数 -> get_batch_data -> load_train_data\n\t测试 -> eval -> load_test_data\n\n\tload_train_data -> create_data\n\tload_test_data -> create_data\n\n\tcreate_data -> load_de_vocab\n\tcreate_data -> load_en_vocab\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 826,-94 826,4 -4,4\" />\n<!-- 训练 -->\n<g id=\"node1\" class=\"node\">\n<title>训练</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"54,-90 0,-90 0,-54 54,-54 54,-90\" />\n<text text-anchor=\"middle\" x=\"27\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">训练</text>\n</g>\n<!-- Graph构造函数 -->\n<g id=\"node2\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"208,-90 90,-90 90,-54 208,-54 208,-90\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 训练->Graph构造函数 -->\n<g id=\"edge1\" class=\"edge\">\n<title>训练->Graph构造函数</title>\n<path fill=\"none\" stroke=\"black\" d=\"M54.08,-72C54.08,-72 79.54,-72 79.54,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"79.54,-75.5 89.54,-72 79.54,-68.5 79.54,-75.5\" />\n</g>\n<!-- get_batch_data -->\n<g id=\"node3\" class=\"node\">\n<title>get_batch_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"369,-90 244,-90 244,-54 369,-54 369,-90\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">get_batch_data</text>\n</g>\n<!-- Graph构造函数->get_batch_data -->\n<g id=\"edge2\" class=\"edge\">\n<title>Graph构造函数->get_batch_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.1,-72C208.1,-72 233.91,-72 233.91,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"233.91,-75.5 243.91,-72 233.91,-68.5 233.91,-75.5\" />\n</g>\n<!-- load_train_data -->\n<g id=\"node4\" class=\"node\">\n<title>load_train_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"531,-90 405,-90 405,-54 531,-54 531,-90\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_train_data</text>\n</g>\n<!-- get_batch_data->load_train_data -->\n<g id=\"edge3\" class=\"edge\">\n<title>get_batch_data->load_train_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M369.4,-72C369.4,-72 394.74,-72 394.74,-72\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"394.74,-75.5 404.74,-72 394.74,-68.5 394.74,-75.5\" />\n</g>\n<!-- create_data -->\n<g id=\"node8\" class=\"node\">\n<title>create_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"667,-63 567,-63 567,-27 667,-27 667,-63\" />\n<text text-anchor=\"middle\" x=\"617\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">create_data</text>\n</g>\n<!-- load_train_data->create_data -->\n<g id=\"edge6\" class=\"edge\">\n<title>load_train_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M531.19,-58.5C531.19,-58.5 556.81,-58.5 556.81,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.81,-62 566.81,-58.5 556.81,-55 556.81,-62\" />\n</g>\n<!-- 测试 -->\n<g id=\"node5\" class=\"node\">\n<title>测试</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"176,-36 122,-36 122,0 176,0 176,-36\" />\n<text text-anchor=\"middle\" x=\"149\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">测试</text>\n</g>\n<!-- eval -->\n<g id=\"node6\" class=\"node\">\n<title>eval</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"333.5,-36 279.5,-36 279.5,0 333.5,0 333.5,-36\" />\n<text text-anchor=\"middle\" x=\"306.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">eval</text>\n</g>\n<!-- 测试->eval -->\n<g id=\"edge4\" class=\"edge\">\n<title>测试->eval</title>\n<path fill=\"none\" stroke=\"black\" d=\"M176.08,-18C176.08,-18 269.25,-18 269.25,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"269.25,-21.5 279.25,-18 269.25,-14.5 269.25,-21.5\" />\n</g>\n<!-- load_test_data -->\n<g id=\"node7\" class=\"node\">\n<title>load_test_data</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"527.5,-36 408.5,-36 408.5,0 527.5,0 527.5,-36\" />\n<text text-anchor=\"middle\" x=\"468\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_test_data</text>\n</g>\n<!-- eval->load_test_data -->\n<g id=\"edge5\" class=\"edge\">\n<title>eval->load_test_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M333.53,-18C333.53,-18 398.34,-18 398.34,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"398.34,-21.5 408.34,-18 398.34,-14.5 398.34,-21.5\" />\n</g>\n<!-- load_test_data->create_data -->\n<g id=\"edge7\" class=\"edge\">\n<title>load_test_data->create_data</title>\n<path fill=\"none\" stroke=\"black\" d=\"M527.75,-31.5C527.75,-31.5 556.82,-31.5 556.82,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"556.82,-35 566.82,-31.5 556.81,-28 556.82,-35\" />\n</g>\n<!-- load_de_vocab -->\n<g id=\"node9\" class=\"node\">\n<title>load_de_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-90 703,-90 703,-54 822,-54 822,-90\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_de_vocab</text>\n</g>\n<!-- create_data->load_de_vocab -->\n<g id=\"edge8\" class=\"edge\">\n<title>create_data->load_de_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-58.5C667.07,-58.5 692.8,-58.5 692.8,-58.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-62 702.8,-58.5 692.8,-55 692.8,-62\" />\n</g>\n<!-- load_en_vocab -->\n<g id=\"node10\" class=\"node\">\n<title>load_en_vocab</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"822,-36 703,-36 703,0 822,0 822,-36\" />\n<text text-anchor=\"middle\" x=\"762.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">load_en_vocab</text>\n</g>\n<!-- create_data->load_en_vocab -->\n<g id=\"edge9\" class=\"edge\">\n<title>create_data->load_en_vocab</title>\n<path fill=\"none\" stroke=\"black\" d=\"M667.07,-31.5C667.07,-31.5 692.8,-31.5 692.8,-31.5\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"692.8,-35 702.8,-31.5 692.8,-28 692.8,-35\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">load_de_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/de.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">load_en_vocab</span><span class=\"p\">():</span>\n <span class=\"n\">vocab</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">'preprocessed/en.vocab.tsv'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">splitlines</span><span class=\"p\">()</span> <span class=\"k\">if</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">line</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()[</span><span class=\"mi\">1</span><span class=\"p\">])</span><span class=\"o\">>=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">min_cnt</span><span class=\"p\">]</span>\n <span class=\"n\">word2idx</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">word</span><span class=\"p\">:</span> <span class=\"n\">idx</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"n\">idx2word</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"n\">idx</span><span class=\"p\">:</span> <span class=\"n\">word</span> <span class=\"k\">for</span> <span class=\"n\">idx</span><span class=\"p\">,</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"n\">vocab</span><span class=\"p\">)}</span>\n <span class=\"k\">return</span> <span class=\"n\">word2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2word</span>\n</code></pre></div></div>\n\n<p>将 <code class=\"language-plaintext highlighter-rouge\">preprocessed/de.vocab.tsv</code> 和 <code class=\"language-plaintext highlighter-rouge\">preprocessed/en.vocab.tsv</code> 中储存的德语、英语的词汇、词频,载入成 <code class=\"language-plaintext highlighter-rouge\">word2idx</code> 和 <code class=\"language-plaintext highlighter-rouge\">idx2word</code>。前者是通过词查询词向量,后者通过词向量查询词。</p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">load_de_vocab</code> 和 <code class=\"language-plaintext highlighter-rouge\">load_en_vocab</code> 函数被 <code class=\"language-plaintext highlighter-rouge\">create_data</code> 函数引用,该函数将输入的源语言和目标语言句子转换为索引表示,并对过长的句子进行截断或填充。详细的解释看下面代码里的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 输入参数是翻译模型的源语言语句、目标语言语句\n</span><span class=\"k\">def</span> <span class=\"nf\">create_data</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 用 zip 函数将源语言和目标语言句子对应起来,并对句子进行截断或填充\n</span> <span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n <span class=\"k\">for</span> <span class=\"n\">source_sent</span><span class=\"p\">,</span> <span class=\"n\">target_sent</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">source_sents</span><span class=\"p\">,</span> <span class=\"n\">target_sents</span><span class=\"p\">):</span>\n <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">de2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">source_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> <span class=\"c1\"># 1: OOV, </S>: End of Text\n</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">en2idx</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">word</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">word</span> <span class=\"ow\">in</span> <span class=\"p\">(</span><span class=\"n\">target_sent</span> <span class=\"o\">+</span> <span class=\"sa\">u</span><span class=\"s\">\" </S>\"</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">()]</span> \n\n <span class=\"c1\"># 将句子的词的编号,原句以及编号后的句子存储下来,以供之后使用\n</span> <span class=\"k\">if</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">),</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span> <span class=\"o\"><=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 将 x 和 y 转换成 numpy 数组并加入 x_list 和 y_list 中\n</span> <span class=\"n\">x_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">))</span>\n <span class=\"n\">y_list</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 将原始的 source_sent 和 target_sent 加入 Sources 和 Targets 列表中\n</span> <span class=\"n\">Sources</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">source_sent</span><span class=\"p\">)</span>\n <span class=\"n\">Targets</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">target_sent</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对于每个 (x, y) 对,使用 np.lib.pad 函数将 x 和 y 分别用 0 进行填充,直到长度为 hp.maxlen\n</span> <span class=\"c1\"># 这样做的目的是使得每个句子长度都相等,方便后续的训练\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">([</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y_list</span><span class=\"p\">),</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">],</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">i</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">)</span> <span class=\"ow\">in</span> <span class=\"nb\">enumerate</span><span class=\"p\">(</span><span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">x_list</span><span class=\"p\">,</span> <span class=\"n\">y_list</span><span class=\"p\">)):</span>\n <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n <span class=\"n\">Y</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">lib</span><span class=\"p\">.</span><span class=\"n\">pad</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"o\">-</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">y</span><span class=\"p\">)],</span> <span class=\"s\">'constant'</span><span class=\"p\">,</span> <span class=\"n\">constant_values</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 返回转换后的索引表示,以及未经处理的源语言和目标语言句子\n</span> <span class=\"c1\"># X 是原始句子中德语的索引\n</span> <span class=\"c1\"># Y 是原始句子中英语的索引\n</span> <span class=\"c1\"># Sources 是源原始句子列表,并与 X 一一对应\n</span> <span class=\"c1\"># Targets 是目标原始句子列表,并与 Y 一一对应\n</span> <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span>\n\n<span class=\"c1\"># 返回原始句子中德语、英语的索引\n</span><span class=\"k\">def</span> <span class=\"nf\">load_train_data</span><span class=\"p\">():</span>\n <span class=\"n\">de_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">source_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n <span class=\"n\">en_sents</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">regex</span><span class=\"p\">.</span><span class=\"n\">sub</span><span class=\"p\">(</span><span class=\"s\">\"[^\\s\\p{Latin}']\"</span><span class=\"p\">,</span> <span class=\"s\">\"\"</span><span class=\"p\">,</span> <span class=\"n\">line</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">target_train</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">,</span> <span class=\"s\">'utf-8'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span> <span class=\"k\">if</span> <span class=\"n\">line</span> <span class=\"ow\">and</span> <span class=\"n\">line</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"o\">!=</span> <span class=\"s\">\"<\"</span><span class=\"p\">]</span>\n \n <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">create_data</span><span class=\"p\">(</span><span class=\"n\">de_sents</span><span class=\"p\">,</span> <span class=\"n\">en_sents</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span>\n</code></pre></div></div>\n\n<p>下面的 <code class=\"language-plaintext highlighter-rouge\">get_batch_data</code> 则从文本数据中读取并生成 batch:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">get_batch_data</span><span class=\"p\">():</span>\n \n <span class=\"c1\"># 加载数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">load_train_data</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># calc total batch count\n</span> <span class=\"n\">num_batch</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span>\n \n <span class=\"c1\"># 将 X 和 Y 转换成张量\n</span> <span class=\"n\">X</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"n\">Y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">Y</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建输入队列\n</span> <span class=\"n\">input_queues</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">slice_input_producer</span><span class=\"p\">([</span><span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Y</span><span class=\"p\">])</span>\n \n <span class=\"c1\"># 创建 batch 队列,利用 shuffle_batch 将一组 tensor 随机打乱,并将它们分为多个 batch\n</span> <span class=\"c1\"># 使用 shuffle_batch 是为了防止模型过拟合\n</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">shuffle_batch</span><span class=\"p\">(</span><span class=\"n\">input_queues</span><span class=\"p\">,</span>\n <span class=\"n\">num_threads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span>\n <span class=\"n\">batch_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> \n <span class=\"n\">capacity</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">64</span><span class=\"p\">,</span> \n <span class=\"n\">min_after_dequeue</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"o\">*</span><span class=\"mi\">32</span><span class=\"p\">,</span> \n <span class=\"n\">allow_smaller_final_batch</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">num_batch</span> <span class=\"c1\"># (N, T), (N, T), ()\n</span></code></pre></div></div>\n\n<h4 id=\"154构建模型并训练\">15.4、构建模型并训练</h4>\n\n<p>Graph 的构造函数流程,就是模型的构建流程,下面船长来分析这部分代码。</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-d43b60bdbb0d309ff41c0875976a1d4b\" width=\"526pt\" height=\"44pt\" viewBox=\"0.00 0.00 526.00 44.00\">\n<title>graphviz-d43b60bdbb0d309ff41c0875976a1d4b</title>\n<desc>\ndigraph G {\n\trankdir=LR\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tGraph构造函数 -> 编码器 -> 解码器 -> Linear -> Softmax\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 40)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-40 522,-40 522,4 -4,4\" />\n<!-- Graph构造函数 -->\n<g id=\"node1\" class=\"node\">\n<title>Graph构造函数</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"118,-36 0,-36 0,0 118,0 118,-36\" />\n<text text-anchor=\"middle\" x=\"59\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Graph构造函数</text>\n</g>\n<!-- 编码器 -->\n<g id=\"node2\" class=\"node\">\n<title>编码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"213,-36 154,-36 154,0 213,0 213,-36\" />\n<text text-anchor=\"middle\" x=\"183.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">编码器</text>\n</g>\n<!-- Graph构造函数->编码器 -->\n<g id=\"edge1\" class=\"edge\">\n<title>Graph构造函数->编码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M118.33,-18C118.33,-18 143.7,-18 143.7,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"143.7,-21.5 153.7,-18 143.7,-14.5 143.7,-21.5\" />\n</g>\n<!-- 解码器 -->\n<g id=\"node3\" class=\"node\">\n<title>解码器</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"308,-36 249,-36 249,0 308,0 308,-36\" />\n<text text-anchor=\"middle\" x=\"278.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">解码器</text>\n</g>\n<!-- 编码器->解码器 -->\n<g id=\"edge2\" class=\"edge\">\n<title>编码器->解码器</title>\n<path fill=\"none\" stroke=\"black\" d=\"M213.04,-18C213.04,-18 238.98,-18 238.98,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"238.98,-21.5 248.98,-18 238.98,-14.5 238.98,-21.5\" />\n</g>\n<!-- Linear -->\n<g id=\"node4\" class=\"node\">\n<title>Linear</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"406,-36 344,-36 344,0 406,0 406,-36\" />\n<text text-anchor=\"middle\" x=\"375\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Linear</text>\n</g>\n<!-- 解码器->Linear -->\n<g id=\"edge3\" class=\"edge\">\n<title>解码器->Linear</title>\n<path fill=\"none\" stroke=\"black\" d=\"M308.24,-18C308.24,-18 333.85,-18 333.85,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"333.85,-21.5 343.85,-18 333.85,-14.5 333.85,-21.5\" />\n</g>\n<!-- Softmax -->\n<g id=\"node5\" class=\"node\">\n<title>Softmax</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"518,-36 442,-36 442,0 518,0 518,-36\" />\n<text text-anchor=\"middle\" x=\"480\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Softmax</text>\n</g>\n<!-- Linear->Softmax -->\n<g id=\"edge4\" class=\"edge\">\n<title>Linear->Softmax</title>\n<path fill=\"none\" stroke=\"black\" d=\"M406.22,-18C406.22,-18 431.65,-18 431.65,-18\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"431.65,-21.5 441.65,-18 431.65,-14.5 431.65,-21.5\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>整体这个流程,主要涉及 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 文件和 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 文件。所有模型所需的主要函数定义,都是在 <code class=\"language-plaintext highlighter-rouge\">modules.py</code> 中实现的。我们先看下编码器(Encoder)的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-4459d4df0778b105cf972d664023b546\" width=\"169pt\" height=\"332pt\" viewBox=\"0.00 0.00 169.00 332.00\">\n<title>graphviz-4459d4df0778b105cf972d664023b546</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\n\tembedding -> positional_encoding -> dropout -> multihead_attention -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 328)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-328 165,-328 165,4 -4,4\" />\n<!-- embedding -->\n<g id=\"node1\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"128.5,-36 32.5,-36 32.5,0 128.5,0 128.5,-36\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node2\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"159.5,-108 1.5,-108 1.5,-72 159.5,-72 159.5,-108\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-36.17C80.5,-36.17 80.5,-61.59 80.5,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-61.59 80.5,-71.59 84,-61.59 77,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node3\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"117,-180 44,-180 44,-144 117,-144 117,-180\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-108.17C80.5,-108.17 80.5,-133.59 80.5,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-133.59 80.5,-143.59 84,-133.59 77,-133.59\" />\n</g>\n<!-- multihead_attention -->\n<g id=\"node4\" class=\"node\">\n<title>multihead_attention</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"161,-252 0,-252 0,-216 161,-216 161,-252\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention</text>\n</g>\n<!-- dropout->multihead_attention -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->multihead_attention</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-180.17C80.5,-180.17 80.5,-205.59 80.5,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-205.59 80.5,-215.59 84,-205.59 77,-205.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node5\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"132.5,-324 28.5,-324 28.5,-288 132.5,-288 132.5,-324\" />\n<text text-anchor=\"middle\" x=\"80.5\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- multihead_attention->feedforward -->\n<g id=\"edge4\" class=\"edge\">\n<title>multihead_attention->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M80.5,-252.17C80.5,-252.17 80.5,-277.59 80.5,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"77,-277.59 80.5,-287.59 84,-277.59 77,-277.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<p>下面是 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 中实现的 Transformer 流程,其中的每一段代码,船长都会做详细解释,先不用急。这个流程里,首先定义了编码器,先使用了 Embedding 层将输入数据转换为词向量,使用 Positional Encoding 层对词向量进行位置编码,使用 Dropout 层进行 dropout 操作,然后进行多层 Multihead Attention 和 Feed Forward 操作。</p>\n\n<p>在构建模型前,先执行 <code class=\"language-plaintext highlighter-rouge\">train.py</code> 的主程序段,首先 <code class=\"language-plaintext highlighter-rouge\">if __name__ == '__main__'</code> 这句代码是在 Python 中常用的一种编写方式,它的意思是当一个文件被直接运行时,<code class=\"language-plaintext highlighter-rouge\">if</code> 语句下面的代码会被执行。请看下面代码的注释。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span> \n \n <span class=\"c1\"># 加载词汇表 \n</span> <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># 构建模型并训练\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"s\">\"train\"</span><span class=\"p\">);</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 创建了一个 Supervisor 对象来管理训练过程\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">(</span><span class=\"n\">graph</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">,</span> \n <span class=\"n\">logdir</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">,</span>\n <span class=\"n\">save_model_secs</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 使用 with 语句打开一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 训练迭代 hp.num_epochs 次\n</span> <span class=\"k\">for</span> <span class=\"n\">epoch</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_epochs</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">):</span> \n <span class=\"k\">if</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">should_stop</span><span class=\"p\">():</span> <span class=\"k\">break</span>\n\n <span class=\"c1\"># tqdm 是一个 Python 库,用来在循环执行训练操作时在命令行中显示进度条\n</span> <span class=\"k\">for</span> <span class=\"n\">step</span> <span class=\"ow\">in</span> <span class=\"n\">tqdm</span><span class=\"p\">(</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">),</span> <span class=\"n\">total</span><span class=\"o\">=</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">num_batch</span><span class=\"p\">,</span> <span class=\"n\">ncols</span><span class=\"o\">=</span><span class=\"mi\">70</span><span class=\"p\">,</span> <span class=\"n\">leave</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> <span class=\"n\">unit</span><span class=\"o\">=</span><span class=\"s\">'b'</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 每次迭代都会运行训练操作 g.train_op\n</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">train_op</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 获取训练的步数,通过 sess.run() 函数获取 global_step 的当前值并赋值给 gs。这样可在后面使用 gs 保存模型时用这个值命名模型\n</span> <span class=\"n\">gs</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 每个 epoch 结束时,它使用 saver.save() 函数保存当前模型的状态\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/model_epoch_%02d_gs_%d'</span> <span class=\"o\">%</span> <span class=\"p\">(</span><span class=\"n\">epoch</span><span class=\"p\">,</span> <span class=\"n\">gs</span><span class=\"p\">))</span>\n \n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 是训练过程中迭代的次数,它表示训练模型需要在训练数据上跑多少遍。每一次迭代都会在训练数据集上进行训练,通常来说,训练数据集会被重复多次迭代,直到达到 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 次。这样可以确保模型能够充分地学习数据的特征。设置 <code class=\"language-plaintext highlighter-rouge\">num_epochs</code> 的值过大或过小都会导致模型性能下降。</li>\n</ul>\n\n<h5 id=\"1541编码过程\">15.4.1、编码过程</h5>\n\n<h6 id=\"embedding\">Embedding</h6>\n\n<p><code class=\"language-plaintext highlighter-rouge\">embedding</code> 用来把输入生成词嵌入向量:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 词语转换为对应的词向量表示\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">de2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 是词汇表的大小。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">num_units</code> 是词向量的维度。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scale</code> 是一个布尔值,用来确定是否对词向量进行标准化。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">scope</code> 是变量作用域的名称。</li>\n</ul>\n\n<h6 id=\"key-masks\">Key Masks</h6>\n\n<p>接着生成一个 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 用于在之后的计算中屏蔽掉某些位置的信息,以便模型只关注有效的信息。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li>先对 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 张量进行对每个元素求绝对值的操作</li>\n <li>沿着最后一阶作为轴,进行 <code class=\"language-plaintext highlighter-rouge\">reduce_sum</code> 操作,得到一个 (batch, sequence_length) 形状的张量。</li>\n <li>再进行 <code class=\"language-plaintext highlighter-rouge\">tf.sign</code> 操作,对刚得到的每个元素进行符号函数的变换。</li>\n <li>最后再扩展阶数,变成形状 (batch, sequence_length, 1) 的张量。</li>\n</ul>\n\n<h6 id=\"positional-encoding\">Positional Encoding</h6>\n\n<p>下面生成 Transformer 的位置编码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"enc_pe\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>如果超参数 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=True</code>,使用 <code class=\"language-plaintext highlighter-rouge\">positional_encoding</code> 函数,通过使用正弦和余弦函数来生成位置编码,可以为输入序列添加位置信息。如果 <code class=\"language-plaintext highlighter-rouge\">hp.sinusoid=False</code>,使用 <code class=\"language-plaintext highlighter-rouge\">embedding</code> 函数,通过学习的词嵌入来生成位置编码。</p>\n\n<p>位置编码生成后,用 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 处理一下。注意 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的生成一定要用最初的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>,所以在前面执行而不是这里:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n</code></pre></div></div>\n\n<p>这个不是矩阵乘法,而是对应元素相乘。这里乘上 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 的目的是将 <code class=\"language-plaintext highlighter-rouge\">key_masks</code> 中值为 0 的位置对应的 <code class=\"language-plaintext highlighter-rouge\">self.enc</code> 中的元素置为 0,这样就可以排除这些位置对计算的影响。</p>\n\n<h6 id=\"drop-out\">Drop out</h6>\n\n<p>下面调用了 TensorFlow 的 drop out 操作:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<p>drop out 是一种在深度学习中常用的正则化技巧。它通过在训练过程中随机地「关闭」一些神经元来减少 <strong>过拟合</strong>。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<p>在这个函数中,<code class=\"language-plaintext highlighter-rouge\">dropout</code> 层通过在训练过程中随机地将一些神经元的输出值设置为 0,来减少模型的过拟合。这个函数中使用了一个参数 <code class=\"language-plaintext highlighter-rouge\">rate</code>,表示每个神经元被「关闭」的概率。这样做是为了防止模型过于依赖于某些特定的特征,而导致在新数据上的表现不佳。</p>\n\n<h6 id=\"encoder-blocks-multi-head-attention--feed-forward\">Encoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<p>然后看下 encoder blocks 代码:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## Blocks\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>上述代码是编码器(Encoder)的实现函数调用的流程,也是与船长上面的模型原理介绍一致的,在定义时同样使用了 Embedding 层、Positional Encoding 层、Dropout 层、Multihead Attention 和 Feed Forward 操作。其中 Multihead Attention 在编码、解码中是不一样的,待会儿我们会在 Decoder 部分再提到,有自注意力层和 Encoder-Decoder 层。</p>\n\n<ul>\n <li>超参数 hp.num_blocks 表示 Encoder Blocks 的层数,每一层都有一个 Multi-Head Attention 和一个 Feed Forward。</li>\n <li>这个 Encoder 中的 Multi-Head Attention 是基于自注意力的(注意与后面的 Decoder 部分有区别)</li>\n <li><code class=\"language-plaintext highlighter-rouge\">causality</code> 参数的意思是否使用 Causal Attention,它是 Self-Attention 的一种,但是只使用过去的信息,防止模型获取未来信息的干扰。一般对于预测序列中的某个时间步来说,只关注之前的信息,而不是整个序列的信息。这段代码中 <code class=\"language-plaintext highlighter-rouge\">causality</code> 设置为了 <code class=\"language-plaintext highlighter-rouge\">False</code>,即会关注整个序列的信息。</li>\n</ul>\n\n<h5 id=\"1542解码过程\">15.4.2、解码过程</h5>\n\n<p>再看一下解码的流程:</p>\n\n<div style=\"text-align: center;\">\n<div class=\"graphviz-wrapper\">\n\n<!-- Generated by graphviz version 2.43.0 (0)\n -->\n<!-- Title: G Pages: 1 -->\n<svg role=\"img\" aria-label=\"graphviz-0ec29ea83329c971f433bc6641585297\" width=\"372pt\" height=\"404pt\" viewBox=\"0.00 0.00 372.00 404.00\">\n<title>graphviz-0ec29ea83329c971f433bc6641585297</title>\n<desc>\ndigraph G {\n\trankdir=BT\n\tsplines=ortho\n\tnode [shape="box"]\n\tdecoder_attn1 [label="multihead_attention (self-attention)"]\n\tdecoder_attn2 [label="multihead_attention (encoder-decoder attention)"]\n\n\tembedding -> positional_encoding -> dropout -> decoder_attn1 -> decoder_attn2 -> feedforward\n}\n</desc>\n\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n<title>G</title>\n<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-400 368,-400 368,4 -4,4\" />\n<!-- decoder_attn1 -->\n<g id=\"node1\" class=\"node\">\n<title>decoder_attn1</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"317,-252 47,-252 47,-216 317,-216 317,-252\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (self-attention)</text>\n</g>\n<!-- decoder_attn2 -->\n<g id=\"node2\" class=\"node\">\n<title>decoder_attn2</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"364,-324 0,-324 0,-288 364,-288 364,-324\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\">multihead_attention (encoder-decoder attention)</text>\n</g>\n<!-- decoder_attn1->decoder_attn2 -->\n<g id=\"edge4\" class=\"edge\">\n<title>decoder_attn1->decoder_attn2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-252.17C182,-252.17 182,-277.59 182,-277.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-277.59 182,-287.59 185.5,-277.59 178.5,-277.59\" />\n</g>\n<!-- feedforward -->\n<g id=\"node6\" class=\"node\">\n<title>feedforward</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"234,-396 130,-396 130,-360 234,-360 234,-396\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\">feedforward</text>\n</g>\n<!-- decoder_attn2->feedforward -->\n<g id=\"edge5\" class=\"edge\">\n<title>decoder_attn2->feedforward</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-324.17C182,-324.17 182,-349.59 182,-349.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-349.59 182,-359.59 185.5,-349.59 178.5,-349.59\" />\n</g>\n<!-- embedding -->\n<g id=\"node3\" class=\"node\">\n<title>embedding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"230,-36 134,-36 134,0 230,0 230,-36\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">embedding</text>\n</g>\n<!-- positional_encoding -->\n<g id=\"node4\" class=\"node\">\n<title>positional_encoding</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"261,-108 103,-108 103,-72 261,-72 261,-108\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">positional_encoding</text>\n</g>\n<!-- embedding->positional_encoding -->\n<g id=\"edge1\" class=\"edge\">\n<title>embedding->positional_encoding</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-36.17C182,-36.17 182,-61.59 182,-61.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-61.59 182,-71.59 185.5,-61.59 178.5,-61.59\" />\n</g>\n<!-- dropout -->\n<g id=\"node5\" class=\"node\">\n<title>dropout</title>\n<polygon fill=\"none\" stroke=\"black\" points=\"218.5,-180 145.5,-180 145.5,-144 218.5,-144 218.5,-180\" />\n<text text-anchor=\"middle\" x=\"182\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">dropout</text>\n</g>\n<!-- positional_encoding->dropout -->\n<g id=\"edge2\" class=\"edge\">\n<title>positional_encoding->dropout</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-108.17C182,-108.17 182,-133.59 182,-133.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-133.59 182,-143.59 185.5,-133.59 178.5,-133.59\" />\n</g>\n<!-- dropout->decoder_attn1 -->\n<g id=\"edge3\" class=\"edge\">\n<title>dropout->decoder_attn1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M182,-180.17C182,-180.17 182,-205.59 182,-205.59\" />\n<polygon fill=\"black\" stroke=\"black\" points=\"178.5,-205.59 182,-215.59 185.5,-205.59 178.5,-205.59\" />\n</g>\n</g>\n</svg>\n</div>\n</div>\n\n<h6 id=\"embedding-1\">Embedding</h6>\n\n<p>下面我们逐一看每段代码,主要关注与编码阶段的区别即可:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">),</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_embed\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">embedding</code> 输入用的是 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>词汇表尺寸用翻译后的输出语言英语词汇表长度 <code class=\"language-plaintext highlighter-rouge\">len(en2idx)</code></li>\n</ul>\n\n<h6 id=\"key-masks-1\">Key Masks</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<ul>\n <li><code class=\"language-plaintext highlighter-rouge\">key_masks</code> 输入变量用 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。</li>\n</ul>\n\n<h6 id=\"positional-encoding--drop-out\">Positional Encoding & Drop out</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 位置编码\n</span><span class=\"k\">if</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">sinusoid</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">positional_encoding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">,</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n<span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">+=</span> <span class=\"n\">embedding</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]),</span> <span class=\"mi\">0</span><span class=\"p\">),</span>\n \t\t\t\t\t\t\t <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">decoder_inputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">]),</span>\n <span class=\"n\">vocab_size</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"dec_pe\"</span><span class=\"p\">)</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">*=</span> <span class=\"n\">key_masks</span>\n\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> \n <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n</code></pre></div></div>\n\n<ul>\n <li>输入 <code class=\"language-plaintext highlighter-rouge\">self.decoder_inputs</code></li>\n <li>指定 <code class=\"language-plaintext highlighter-rouge\">vocab_size</code> 参数 <code class=\"language-plaintext highlighter-rouge\">hp.maxlen</code></li>\n</ul>\n\n<h6 id=\"decoder-blocks-multi-head-attention--feed-forward\">Decoder Blocks: Multi-Head Attention & Feed Forward</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">## 解码器模块\n</span><span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_blocks</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"s\">\"num_blocks_{}\"</span><span class=\"p\">.</span><span class=\"nb\">format</span><span class=\"p\">(</span><span class=\"n\">i</span><span class=\"p\">)):</span>\n <span class=\"c1\"># 多头注意力(自注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"self_attention\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 多头注意力(Encoder-Decoder 注意力)\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">enc</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">num_heads</span><span class=\"p\">,</span>\n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"n\">is_training</span><span class=\"p\">,</span> \n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"vanilla_attention\"</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 前馈神经网络\n</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span> <span class=\"o\">=</span> <span class=\"n\">feedforward</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">4</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">hidden_units</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<ul>\n <li>在用 <code class=\"language-plaintext highlighter-rouge\">multihead_attention</code> 函数解码器模块时,注意传入的参数 <code class=\"language-plaintext highlighter-rouge\">scope</code> 区别,先是自注意力层,用参数 <code class=\"language-plaintext highlighter-rouge\">self_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,<code class=\"language-plaintext highlighter-rouge\">keys</code> 也是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>。再是「Encoder-Decder Attention」用的是参数 <code class=\"language-plaintext highlighter-rouge\">vanilla_attention</code>,对应的 <code class=\"language-plaintext highlighter-rouge\">queries</code> 来自解码器是 <code class=\"language-plaintext highlighter-rouge\">self.dec</code>,但 <code class=\"language-plaintext highlighter-rouge\">keys</code> 来自编码器是是 <code class=\"language-plaintext highlighter-rouge\">self.enc</code>。</li>\n</ul>\n\n<h5 id=\"1543embeddingpositional-encodingmulti-head-attentionfeed-forward\">15.4.3、Embedding、Positional Encoding、Multi-Head Attention、Feed Forward</h5>\n\n<h6 id=\"embedding-函数实现\">Embedding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">embedding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">vocab_size</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"p\">,</span> \n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span> \n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"embedding\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 创建一个名为 `lookup_table`、形状为 (vocab_size, num_units) 的矩阵\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">get_variable</span><span class=\"p\">(</span><span class=\"s\">'lookup_table'</span><span class=\"p\">,</span>\n <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">float32</span><span class=\"p\">,</span>\n <span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"n\">vocab_size</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">],</span>\n <span class=\"n\">initializer</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">contrib</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">xavier_initializer</span><span class=\"p\">())</span>\n\n <span class=\"c1\"># lookup_table 的第一行插入一个全零行,作为 PAD 的词向量\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 在词向量矩阵 lookup_table 中查找 inputs\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 对输出的词向量进行除以根号 num_units 的操作,可以控制词向量的统计稳定性。\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"p\">(</span><span class=\"n\">num_units</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span> \n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"positional-encoding-函数实现\">Positional Encoding 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">positional_encoding</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span>\n <span class=\"n\">num_units</span><span class=\"p\">,</span>\n <span class=\"n\">zero_pad</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scale</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"positional_encoding\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n\n <span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"n\">T</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># tf.range(T) 生成一个 0~T-1 的数组\n</span> \t<span class=\"c1\"># tf.tile() 将其扩展成 N*T 的矩阵,表示每个词的位置\n</span> <span class=\"n\">position_ind</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">),</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">N</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span>\n\n <span class=\"c1\"># First part of the PE function: sin and cos argument\n</span> <span class=\"n\">position_enc</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">array</span><span class=\"p\">([</span>\n <span class=\"p\">[</span><span class=\"n\">pos</span> <span class=\"o\">/</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">power</span><span class=\"p\">(</span><span class=\"mi\">10000</span><span class=\"p\">,</span> <span class=\"mf\">2.</span><span class=\"o\">*</span><span class=\"n\">i</span><span class=\"o\">/</span><span class=\"n\">num_units</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">num_units</span><span class=\"p\">)]</span>\n <span class=\"k\">for</span> <span class=\"n\">pos</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">T</span><span class=\"p\">)])</span>\n\n <span class=\"c1\"># 用 numpy 的 sin 和 cos 函数对每个位置进行编码\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">sin</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">0</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i\n</span> <span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">cos</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">[:,</span> <span class=\"mi\">1</span><span class=\"p\">::</span><span class=\"mi\">2</span><span class=\"p\">])</span> <span class=\"c1\"># dim 2i+1\n</span>\n <span class=\"c1\"># 将编码结果转为张量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">position_enc</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 将编码的结果与位置索引相关联,得到最终的位置编码\n</span> <span class=\"k\">if</span> <span class=\"n\">zero_pad</span><span class=\"p\">:</span>\n \t<span class=\"c1\"># 如果 zero_pad 参数为 True,则在编码结果的开头添加一个全 0 的向量\n</span> <span class=\"n\">lookup_table</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">((</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">shape</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">]),</span>\n <span class=\"n\">lookup_table</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:,</span> <span class=\"p\">:]),</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">embedding_lookup</span><span class=\"p\">(</span><span class=\"n\">lookup_table</span><span class=\"p\">,</span> <span class=\"n\">position_ind</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># scale 参数为 True,则将编码结果乘上 num_units 的平方根\n</span> <span class=\"k\">if</span> <span class=\"n\">scale</span><span class=\"p\">:</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">*</span> <span class=\"n\">num_units</span><span class=\"o\">**</span><span class=\"mf\">0.5</span>\n\n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"multi-head-attention-函数实现\">Multi-Head Attention 函数实现</h6>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">multihead_attention</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> \n <span class=\"n\">keys</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">,</span> \n <span class=\"n\">num_heads</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">,</span> \n <span class=\"n\">dropout_rate</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">,</span>\n <span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"n\">causality</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Set the fall back option for num_units\n</span> <span class=\"k\">if</span> <span class=\"n\">num_units</span> <span class=\"ow\">is</span> <span class=\"bp\">None</span><span class=\"p\">:</span>\n <span class=\"n\">num_units</span> <span class=\"o\">=</span> <span class=\"n\">queries</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># Linear Projections\n</span> <span class=\"c1\"># 使用三个全连接层对输入的 queries、keys 分别进行线性变换,将其转换为三个维度相同的张量 Q/K/V\n</span> <span class=\"n\">Q</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> <span class=\"n\">V</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">,</span> <span class=\"n\">num_units</span><span class=\"p\">,</span> <span class=\"n\">activation</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_k, C)\n</span> \n <span class=\"c1\"># Split and concat\n</span> <span class=\"c1\"># 按头数 split Q/K/V,再各自连接起来\n</span> <span class=\"n\">Q_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">Q</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, C/h) \n</span> <span class=\"n\">K_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">K</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span> <span class=\"n\">V_</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">V</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_k, C/h) \n</span>\n <span class=\"c1\"># Multiplication\n</span> <span class=\"c1\"># 计算 Q_, K_, V_ 的点积来获得注意力权重\n</span> <span class=\"c1\"># 其中 Q_ 的维度为 (hN, T_q, C/h)\n</span> <span class=\"c1\"># K_ 的维度为 (hN, T_k, C/h)\n</span> <span class=\"c1\"># 计算出来的结果 outputs 的维度为 (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">Q_</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">transpose</span><span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">]))</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span>\n <span class=\"c1\"># Scale\n</span> <span class=\"c1\"># 对权重进行 scale,这里除以了 K_ 的第三维的平方根,用于缩放权重\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">K_</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"o\">**</span> <span class=\"mf\">0.5</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Key Masking\n</span> <span class=\"c1\"># 这里需要将 keys 的有效部分标记出来,将无效部分设置为极小值,以便在之后的 softmax 中被忽略\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_k)\n</span> <span class=\"n\">key_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">key_masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Causality = Future blinding\n</span> <span class=\"k\">if</span> <span class=\"n\">causality</span><span class=\"p\">:</span>\n\n \t<span class=\"c1\"># 创建一个与 outputs[0, :, :] 相同形状的全 1 矩阵\n</span> <span class=\"n\">diag_vals</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"p\">:,</span> <span class=\"p\">:])</span> <span class=\"c1\"># (T_q, T_k)\n</span>\n <span class=\"c1\"># 对 diag_vals 进行处理,返回一个下三角线矩阵\n</span> <span class=\"n\">tril</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">LinearOperatorLowerTriangular</span><span class=\"p\">(</span><span class=\"n\">diag_vals</span><span class=\"p\">).</span><span class=\"n\">to_dense</span><span class=\"p\">()</span> <span class=\"c1\"># (T_q, T_k)\n</span> <span class=\"n\">masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">tril</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n \t\t\t<span class=\"c1\"># 将 masks 为 0 的位置的 outputs 值设置为一个非常小的数\n</span> \t\t\t<span class=\"c1\"># 这样会导致这些位置在之后的计算中对结果产生非常小的影响,从而实现了遮盖未来信息的功能\n</span> <span class=\"n\">paddings</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones_like</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"p\">(</span><span class=\"o\">-</span><span class=\"mi\">2</span><span class=\"o\">**</span><span class=\"mi\">32</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">where</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"n\">masks</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">paddings</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># 对于每个头的输出,应用 softmax 激活函数,这样可以得到一个概率分布\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> \n <span class=\"c1\"># Query Masking\n</span> <span class=\"c1\"># 对于查询(queries)进行 masking,这样可以避免输入序列后面的词对之前词的影响\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">sign</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"nb\">abs</span><span class=\"p\">(</span><span class=\"n\">queries</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span> <span class=\"c1\"># (N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">])</span> <span class=\"c1\"># (h*N, T_q)\n</span> <span class=\"n\">query_masks</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">tile</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">expand_dims</span><span class=\"p\">(</span><span class=\"n\">query_masks</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">),</span> <span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">(</span><span class=\"n\">keys</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]])</span> <span class=\"c1\"># (h*N, T_q, T_k)\n</span> <span class=\"n\">outputs</span> <span class=\"o\">*=</span> <span class=\"n\">query_masks</span> <span class=\"c1\"># broadcasting. (N, T_q, C)\n</span> \n <span class=\"c1\"># Dropouts & Weighted Sum\n</span> <span class=\"c1\"># 对于每个头的输出,应用 dropout 以及进行残差连接\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dropout</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">rate</span><span class=\"o\">=</span><span class=\"n\">dropout_rate</span><span class=\"p\">,</span> <span class=\"n\">training</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">convert_to_tensor</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"p\">))</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">matmul</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">V_</span><span class=\"p\">)</span> <span class=\"c1\"># ( h*N, T_q, C/h)\n</span> \n <span class=\"c1\"># Restore shape\n</span> <span class=\"c1\"># 将每个头的输出拼接起来,使用 tf.concat 函数,将不同头的结果按照第二维拼接起来\n</span> <span class=\"c1\"># 得到最终的输出结果,即经过多头注意力计算后的结果\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">concat</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">num_heads</span><span class=\"p\">,</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">),</span> <span class=\"n\">axis</span><span class=\"o\">=</span><span class=\"mi\">2</span> <span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"c1\"># Residual connection\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">queries</span>\n \n <span class=\"c1\"># Normalize\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span> <span class=\"c1\"># (N, T_q, C)\n</span> \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<h6 id=\"feed-forward-函数实现\">Feed Forward 函数实现</h6>\n\n<p>下面是 <strong>前馈神经网络层</strong> 的定义,这是一个非线性变换,这里用到了一些卷积神经网络(CNN)的知识,我们来看下代码再解释:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">feedforward</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">num_units</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mi\">2048</span><span class=\"p\">,</span> <span class=\"mi\">512</span><span class=\"p\">],</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"multihead_attention\"</span><span class=\"p\">,</span> \n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n <span class=\"c1\"># Inner layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">relu</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># Readout layer\n</span> <span class=\"n\">params</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"inputs\"</span><span class=\"p\">:</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"s\">\"filters\"</span><span class=\"p\">:</span> <span class=\"n\">num_units</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"s\">\"kernel_size\"</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span class=\"p\">,</span>\n <span class=\"s\">\"activation\"</span><span class=\"p\">:</span> <span class=\"bp\">None</span><span class=\"p\">,</span> <span class=\"s\">\"use_bias\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">}</span>\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">conv1d</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">params</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 连接一个残差网络 ResNet\n</span> <span class=\"n\">outputs</span> <span class=\"o\">+=</span> <span class=\"n\">inputs</span>\n \n <span class=\"c1\"># 归一化后输出\n</span> <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">normalize</span><span class=\"p\">(</span><span class=\"n\">outputs</span><span class=\"p\">)</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>先是使用了一个卷积层(conv1d)作为 inner layer、一个卷积层作为 readout layer,卷积核大小都为 1。</li>\n <li><code class=\"language-plaintext highlighter-rouge\">filters</code> 参数用来控制卷积层中输出通道数量,inner layer 的输出通道数设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[0]</code> ,readout layer 的设置为 <code class=\"language-plaintext highlighter-rouge\">num_units[1]</code>。有时也会把这个解释为神经元数量。这两个的默认分别为 2048、512,调用时传入的是超参数的 <code class=\"language-plaintext highlighter-rouge\">[4 * hidden_units, hidden_units]</code>。</li>\n <li>其中 inner layer 用 <code class=\"language-plaintext highlighter-rouge\">ReLU</code> 作为激活函数,然后连接一个残差网络 RedNet,把 readout layer 的输出加上原始的输入。</li>\n <li>最后使用 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 归一化处理输出,再返回。下面来看下 <code class=\"language-plaintext highlighter-rouge\">normalize</code> 函数。</li>\n</ul>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">normalize</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> \n <span class=\"n\">epsilon</span> <span class=\"o\">=</span> <span class=\"mf\">1e-8</span><span class=\"p\">,</span>\n <span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s\">\"ln\"</span><span class=\"p\">,</span>\n <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"bp\">None</span><span class=\"p\">):</span>\n <span class=\"k\">with</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">variable_scope</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"p\">,</span> <span class=\"n\">reuse</span><span class=\"o\">=</span><span class=\"n\">reuse</span><span class=\"p\">):</span>\n\n \t<span class=\"c1\"># 输入数据的形状\n</span> <span class=\"n\">inputs_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">()</span>\n <span class=\"n\">params_shape</span> <span class=\"o\">=</span> <span class=\"n\">inputs_shape</span><span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">:]</span>\n \n \t<span class=\"c1\"># 平均数、方差\n</span> <span class=\"n\">mean</span><span class=\"p\">,</span> <span class=\"n\">variance</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">moments</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"p\">[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"n\">keep_dims</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 拉伸因子 beta\n</span> <span class=\"n\">beta</span><span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 缩放因子 gamma\n</span> <span class=\"n\">gamma</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ones</span><span class=\"p\">(</span><span class=\"n\">params_shape</span><span class=\"p\">))</span>\n\n <span class=\"c1\"># 归一化:加上一个非常小的 epsilon,是为了防止除以 0\n</span> <span class=\"n\">normalized</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span> <span class=\"o\">-</span> <span class=\"n\">mean</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span> <span class=\"p\">(</span><span class=\"n\">variance</span> <span class=\"o\">+</span> <span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">**</span> <span class=\"p\">(.</span><span class=\"mi\">5</span><span class=\"p\">)</span> <span class=\"p\">)</span>\n\n <span class=\"n\">outputs</span> <span class=\"o\">=</span> <span class=\"n\">gamma</span> <span class=\"o\">*</span> <span class=\"n\">normalized</span> <span class=\"o\">+</span> <span class=\"n\">beta</span>\n \n <span class=\"k\">return</span> <span class=\"n\">outputs</span>\n</code></pre></div></div>\n\n<ul>\n <li>该函数实现了 Layer Normalization,用于在深度神经网络中解决数据的不稳定性问题。</li>\n</ul>\n\n<h5 id=\"1544编码和解码完成后的操作\">15.4.4、编码和解码完成后的操作</h5>\n\n<p>解码器后的 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code>:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 全连接层得到的未经过归一化的概率值\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">layers</span><span class=\"p\">.</span><span class=\"n\">dense</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">dec</span><span class=\"p\">,</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 预测的英文单词 idx\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_int32</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">arg_max</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">dimension</span><span class=\"o\">=-</span><span class=\"mi\">1</span><span class=\"p\">))</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">not_equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 正确预测数量,除以所有样本数,得到准确率\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">to_float</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">equal</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">))</span><span class=\"o\">*</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span><span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># 记录了模型的准确率的值,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'acc'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">acc</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>训练集数据处理时,经过 <code class=\"language-plaintext highlighter-rouge\">Linear & Softmax</code> 之后的最后处理如下。这里用到了 <code class=\"language-plaintext highlighter-rouge\">tf.nn.softmax_cross_entropy_with_logits</code> 交叉熵损失,来计算模型的错误率 <code class=\"language-plaintext highlighter-rouge\">mean_loss</code>,并使用 Adam 优化器 <code class=\"language-plaintext highlighter-rouge\">AdamOptimizer</code> 来优化模型参数。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 使用 label_smoothing 函数对真实标签进行标签平滑,得到 self.y_smoothed\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span> <span class=\"o\">=</span> <span class=\"n\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">one_hot</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">depth</span><span class=\"o\">=</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">en2idx</span><span class=\"p\">)))</span>\n</code></pre></div></div>\n\n<p>下面这段代码实现了一种叫做「label Smoothing」的技巧。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">label_smoothing</span><span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">):</span>\n\n\t<span class=\"c1\"># 获取输入的类别数,并将其赋值给变量 K\n</span> <span class=\"n\">K</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">.</span><span class=\"n\">get_shape</span><span class=\"p\">().</span><span class=\"n\">as_list</span><span class=\"p\">()[</span><span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># number of channels\n</span> <span class=\"k\">return</span> <span class=\"p\">((</span><span class=\"mi\">1</span><span class=\"o\">-</span><span class=\"n\">epsilon</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">inputs</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span class=\"p\">(</span><span class=\"n\">epsilon</span> <span class=\"o\">/</span> <span class=\"n\">K</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在训练过程中,样本的标签被表示为一个二维矩阵,其中第一维表示样本的编号,第二维表示样本的标签。这个矩阵的形状就是 (样本数, 类别数),所以类别数对应的就是最后一维。具体到这个模型用例里,第一个维度是德语样本句子数,最后一维就是英语词汇量的大小。</p>\n\n<p>用于解决在训练模型时出现的过拟合问题。在标签平滑中,我们给每个样本的标签加上一些噪声,使得模型不能完全依赖于样本的标签来进行训练,从而减少过拟合的可能性。具体来说,这段代码将输入的标签 <code class=\"language-plaintext highlighter-rouge\">inputs</code> 乘上 <code class=\"language-plaintext highlighter-rouge\">1-epsilon</code>,再加上 <code class=\"language-plaintext highlighter-rouge\">epsilon / K</code>,其中 <code class=\"language-plaintext highlighter-rouge\">epsilon</code> 是平滑因子,<code class=\"language-plaintext highlighter-rouge\">K</code> 是标签类别数(英语词汇量大小)。这样就可以在训练过程中让模型对标签的预测更加平稳,并且降低过拟合的风险。</p>\n\n<p>然后我们看后续的操作。</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># 对于分类问题来说,常用的损失函数是交叉熵损失\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">nn</span><span class=\"p\">.</span><span class=\"n\">softmax_cross_entropy_with_logits</span><span class=\"p\">(</span><span class=\"n\">logits</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">logits</span><span class=\"p\">,</span> <span class=\"n\">labels</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">y_smoothed</span><span class=\"p\">)</span>\n<span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">loss</span> <span class=\"o\">*</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">reduce_sum</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">istarget</span><span class=\"p\">))</span>\n\n<span class=\"c1\"># Training Scheme\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">Variable</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s\">'global_step'</span><span class=\"p\">,</span> <span class=\"n\">trainable</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Adam 优化器 self.optimizer,用于优化损失函数\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">AdamOptimizer</span><span class=\"p\">(</span><span class=\"n\">learning_rate</span><span class=\"o\">=</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">lr</span><span class=\"p\">,</span> <span class=\"n\">beta1</span><span class=\"o\">=</span><span class=\"mf\">0.9</span><span class=\"p\">,</span> <span class=\"n\">beta2</span><span class=\"o\">=</span><span class=\"mf\">0.98</span><span class=\"p\">,</span> <span class=\"n\">epsilon</span><span class=\"o\">=</span><span class=\"mf\">1e-8</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 使用优化器的 minimize() 函数创建一个训练操作 self.train_op,用于更新模型参数。这个函数会自动计算梯度并应用更新\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">train_op</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">optimizer</span><span class=\"p\">.</span><span class=\"n\">minimize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">,</span> <span class=\"n\">global_step</span><span class=\"o\">=</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">global_step</span><span class=\"p\">)</span>\n \n<span class=\"c1\"># 将平均损失写入 TensorFlow 的 Summary 中,用于 tensorboard 可视化\n</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">scalar</span><span class=\"p\">(</span><span class=\"s\">'mean_loss'</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">mean_loss</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># 将所有的 summary 合并到一起,方便在训练过程中写入事件文件\n</span><span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">merged</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">summary</span><span class=\"p\">.</span><span class=\"n\">merge_all</span><span class=\"p\">()</span>\n</code></pre></div></div>\n\n<h4 id=\"155效果评价\">15.5、效果评价</h4>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">eval</span><span class=\"p\">():</span> \n <span class=\"c1\"># 创建一个处理测试数据集的 Graph 实例\n</span> <span class=\"n\">g</span> <span class=\"o\">=</span> <span class=\"n\">Graph</span><span class=\"p\">(</span><span class=\"n\">is_training</span><span class=\"o\">=</span><span class=\"bp\">False</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Graph loaded\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 加载测试数据\n</span> <span class=\"n\">X</span><span class=\"p\">,</span> <span class=\"n\">Sources</span><span class=\"p\">,</span> <span class=\"n\">Targets</span> <span class=\"o\">=</span> <span class=\"n\">load_test_data</span><span class=\"p\">()</span>\n <span class=\"n\">de2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2de</span> <span class=\"o\">=</span> <span class=\"n\">load_de_vocab</span><span class=\"p\">()</span>\n <span class=\"n\">en2idx</span><span class=\"p\">,</span> <span class=\"n\">idx2en</span> <span class=\"o\">=</span> <span class=\"n\">load_en_vocab</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># Start session \n</span> <span class=\"k\">with</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">graph</span><span class=\"p\">.</span><span class=\"n\">as_default</span><span class=\"p\">():</span>\n\n \t<span class=\"c1\"># TensorFlow 中用于管理训练的一个类\n</span> \t<span class=\"c1\"># 它可以帮助你轻松地管理训练过程中的各种资源,如模型参数、检查点和日志\n</span> <span class=\"n\">sv</span> <span class=\"o\">=</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">Supervisor</span><span class=\"p\">()</span>\n\n <span class=\"c1\"># 创建一个会话\n</span> <span class=\"k\">with</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">managed_session</span><span class=\"p\">(</span><span class=\"n\">config</span><span class=\"o\">=</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">ConfigProto</span><span class=\"p\">(</span><span class=\"n\">allow_soft_placement</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">sess</span><span class=\"p\">:</span>\n\n <span class=\"c1\"># 恢复模型参数\n</span> <span class=\"n\">sv</span><span class=\"p\">.</span><span class=\"n\">saver</span><span class=\"p\">.</span><span class=\"n\">restore</span><span class=\"p\">(</span><span class=\"n\">sess</span><span class=\"p\">,</span> <span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">train</span><span class=\"p\">.</span><span class=\"n\">latest_checkpoint</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span><span class=\"p\">))</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Restored!\"</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 获取模型名称\n</span> <span class=\"n\">mname</span> <span class=\"o\">=</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">logdir</span> <span class=\"o\">+</span> <span class=\"s\">'/checkpoint'</span><span class=\"p\">,</span> <span class=\"s\">'r'</span><span class=\"p\">).</span><span class=\"n\">read</span><span class=\"p\">().</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">'\"'</span><span class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span> <span class=\"c1\"># model name\n</span> \n <span class=\"c1\">## Inference\n</span> <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">path</span><span class=\"p\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">):</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">mkdir</span><span class=\"p\">(</span><span class=\"s\">'results'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 初始化结果文件\n</span> <span class=\"k\">with</span> <span class=\"n\">codecs</span><span class=\"p\">.</span><span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s\">\"results/\"</span> <span class=\"o\">+</span> <span class=\"n\">mname</span><span class=\"p\">,</span> <span class=\"s\">\"w\"</span><span class=\"p\">,</span> <span class=\"s\">\"utf-8\"</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">fout</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span> <span class=\"o\">=</span> <span class=\"p\">[],</span> <span class=\"p\">[]</span>\n\n <span class=\"c1\"># 循环处理数据\n</span> <span class=\"k\">for</span> <span class=\"n\">i</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">X</span><span class=\"p\">)</span> <span class=\"o\">//</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">):</span>\n \n <span class=\"c1\"># 获取小批量数据\n</span> <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">X</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">sources</span> <span class=\"o\">=</span> <span class=\"n\">Sources</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n <span class=\"n\">targets</span> <span class=\"o\">=</span> <span class=\"n\">Targets</span><span class=\"p\">[</span><span class=\"n\">i</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">:</span> <span class=\"p\">(</span><span class=\"n\">i</span><span class=\"o\">+</span><span class=\"mi\">1</span><span class=\"p\">)</span><span class=\"o\">*</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 使用自回归推理(Autoregressive inference)得到预测结果\n</span> <span class=\"n\">preds</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">batch_size</span><span class=\"p\">,</span> <span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">),</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">int32</span><span class=\"p\">)</span>\n <span class=\"k\">for</span> <span class=\"n\">j</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">hp</span><span class=\"p\">.</span><span class=\"n\">maxlen</span><span class=\"p\">):</span>\n <span class=\"n\">_preds</span> <span class=\"o\">=</span> <span class=\"n\">sess</span><span class=\"p\">.</span><span class=\"n\">run</span><span class=\"p\">(</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">preds</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">g</span><span class=\"p\">.</span><span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"n\">preds</span><span class=\"p\">})</span>\n <span class=\"n\">preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">_preds</span><span class=\"p\">[:,</span> <span class=\"n\">j</span><span class=\"p\">]</span>\n \n <span class=\"c1\"># 将预测结果写入文件\n</span> <span class=\"k\">for</span> <span class=\"n\">source</span><span class=\"p\">,</span> <span class=\"n\">target</span><span class=\"p\">,</span> <span class=\"n\">pred</span> <span class=\"ow\">in</span> <span class=\"nb\">zip</span><span class=\"p\">(</span><span class=\"n\">sources</span><span class=\"p\">,</span> <span class=\"n\">targets</span><span class=\"p\">,</span> <span class=\"n\">preds</span><span class=\"p\">):</span> <span class=\"c1\"># sentence-wise\n</span> <span class=\"n\">got</span> <span class=\"o\">=</span> <span class=\"s\">\" \"</span><span class=\"p\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">idx2en</span><span class=\"p\">[</span><span class=\"n\">idx</span><span class=\"p\">]</span> <span class=\"k\">for</span> <span class=\"n\">idx</span> <span class=\"ow\">in</span> <span class=\"n\">pred</span><span class=\"p\">).</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s\">\"</S>\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">strip</span><span class=\"p\">()</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- source: \"</span> <span class=\"o\">+</span> <span class=\"n\">source</span> <span class=\"o\">+</span><span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- expected: \"</span> <span class=\"o\">+</span> <span class=\"n\">target</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"- got: \"</span> <span class=\"o\">+</span> <span class=\"n\">got</span> <span class=\"o\">+</span> <span class=\"s\">\"</span><span class=\"se\">\\n\\n</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n \n <span class=\"c1\"># bleu score\n</span> <span class=\"n\">ref</span> <span class=\"o\">=</span> <span class=\"n\">target</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"n\">hypothesis</span> <span class=\"o\">=</span> <span class=\"n\">got</span><span class=\"p\">.</span><span class=\"n\">split</span><span class=\"p\">()</span>\n <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">ref</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span> <span class=\"ow\">and</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span> <span class=\"o\">></span> <span class=\"mi\">3</span><span class=\"p\">:</span>\n <span class=\"n\">list_of_refs</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">([</span><span class=\"n\">ref</span><span class=\"p\">])</span>\n <span class=\"n\">hypotheses</span><span class=\"p\">.</span><span class=\"n\">append</span><span class=\"p\">(</span><span class=\"n\">hypothesis</span><span class=\"p\">)</span>\n \n <span class=\"c1\"># 计算 BLEU 分数,并将其写入文件\n</span> <span class=\"n\">score</span> <span class=\"o\">=</span> <span class=\"n\">corpus_bleu</span><span class=\"p\">(</span><span class=\"n\">list_of_refs</span><span class=\"p\">,</span> <span class=\"n\">hypotheses</span><span class=\"p\">)</span>\n <span class=\"n\">fout</span><span class=\"p\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span class=\"s\">\"Bleu Score = \"</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"mi\">100</span><span class=\"o\">*</span><span class=\"n\">score</span><span class=\"p\">))</span>\n \n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">'__main__'</span><span class=\"p\">:</span>\n <span class=\"nb\">eval</span><span class=\"p\">()</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Done\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h3 id=\"第-16-节--kyubyong-transformer-的性能表现和一些问题\">第 16 节 · Kyubyong Transformer 的性能表现和一些问题</h3>\n\n<p>评估结果文件的最后一行有 Bleu Score = 6.598452846670836 表示这个翻译模型的翻译结果与参考翻译重叠程度比较高,翻译质量较好。不过需要注意的是,BLEU 分数不能完全反映翻译质量,因为它不能评估语法,语义,语调等方面的问题。</p>\n\n<p>另外前面我们在代码中已经将过程数据保存在 logdir 下了,就是为了后续方便可视化,我们可以用 TensorBoard 来可视化,具体使用方法如下:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>mikecaptain@local <span class=\"nv\">$ </span>tensorboard <span class=\"nt\">--logdir</span> logdir\n</code></pre></div></div>\n\n<p>然后在浏览器里查看 <code class=\"language-plaintext highlighter-rouge\">http://localhost:6006</code>,示例如下:</p>\n\n<p><img src=\"/img/src/2023-01-04-language-model-5-17.gif\" alt=\"image\" /></p>\n\n<p>我们可以看到这个 Transformer 能够较好地捕捉长距离依赖关系,提高翻译质量。然而,Kyubyong Transformer 的实现存在一些问题。该 Transformer 模型在训练过程中还需要调整许多超参数,如学习率(learning rate)、batch size 等,不同的任务可能需要不同的超参数调整。</p>\n\n<h2 id=\"结尾--transformer-问世后的这些年\">结尾 · Transformer 问世后的这些年</h2>\n\n<p>Transformer 的优势显而易见:</p>\n\n<ul>\n <li>更快 —— 并行性好:在 Transformer 诞生之前,RNN 是 NLP 领域的主流模型,但是 RNN 并行性差(序列串行处理)。</li>\n <li>不健忘 —— 词距离缩短为 1:RNN 模型处理长文本内容已丢失(在 RNN 模型中意味着词的空间距离长)。</li>\n <li>处理不同长度序列:不需要输入数据的序列是固定长度的。</li>\n <li>易于转移学习。</li>\n</ul>\n\n<p>因此基于 Transformer 原理的模型,在众多 NLP 任务中都取得了卓越的表现。</p>\n\n<p>说到底机器学习(Machine Learning)领域还是一个实验科学,并且是离工业界极近的实验科学。机器学习看待实验结果的角度,不是为了拿实验结果总结抽象后推动理论科学发展。机器学习的实验结果是要被评价的,其效果有客观量化评估标准。所以机器学习,一切以结果说话。基于 Transformer 架构 Decoder 部分诞生了 OpenAI 的 GPT 大模型,基于其架构的 Encoder 部分诞生了 Google 的 BERT 大模型,他们两个都诞生于 2018 年。这几年基于 Transformer 的各种优化思想不断出现,其集大成者便是 2022 年年底基于 GPT-3.5 或者说基于 InstructGPT 的 ChatGPT。</p>\n\n<p>感谢你有耐心看完本篇近 10 万字长文,因为是船涨的技术笔记,所以对于关键点梳理得细致了些。后续,我讲和大家一起聊聊 AIGC 的当下,如果说本篇内容更像一个教程(对缘起技术的深入),那么后续我们的探讨则可能更像一篇报告了(对眼前学界与业界发展现状的综述),我们将更关注文章「前言」部分的两个议题:1)如果认为通过图灵测试代表着 AGI(Artificial General Intelligence,通用人工智能)的话,当下 NLP,乃至 AGI 发展到什么程度了?2)未来一些年内,AGI 的发展路线可能会是怎样的?</p>\n\n<p>AI 终将颠覆各行各业,阿里人有责任花些时间关注前沿的发展脉搏,欢迎大家在钉钉或微信(id:sinosuperman)上与我交流。</p>\n\n<p>最后,船涨祝大家兔年里,健康又快乐。</p>\n\n<p>PS:</p>\n\n<blockquote>\n <ul>\n <li>UPDATED:2023 年 1 月 27 日,本文登上 ATA 头条。(注:ATA 全称 Alibaba Technology Associate,是阿里集团最大的技术社区)</li>\n <li>UPDATED:2023 年 2 月 2 日,本文在 ATA 获得鲁肃点赞。(注:鲁肃,本名程立,是阿里合伙人、阿里集团上一任 CTO)</li>\n </ul>\n</blockquote>\n\n<p><img src=\"/img/src/2023/2023-01-29-ata-headline-top-1.jpg\" alt=\"image\" />\n<img src=\"/img/src/2023/2023-01-29-ata-headline-top-2.png\" alt=\"image\" /></p>\n\n<h2 id=\"参考\">参考</h2>\n\n<ul>\n <li>https://web.stanford.edu/~jurafsky/slp3/3.pdf</li>\n <li>https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html</li>\n <li>《自然语言处理:基于预训练模型的方法》车万翔 等著</li>\n <li>https://cs.stanford.edu/people/karpathy/convnetjs/</li>\n <li>https://arxiv.org/abs/1706.03762</li>\n <li>https://arxiv.org/abs/1512.03385</li>\n <li>https://github.com/Kyubyong/transformer/</li>\n <li>http://jalammar.github.io/illustrated-transformer/</li>\n <li>https://towardsdatascience.com/this-is-how-to-train-better-transformer-models-d54191299978</li>\n <li>《自然语言处理实战:预训练模型应用及其产品化》安库·A·帕特尔 等著</li>\n <li>https://lilianweng.github.io/posts/2018-06-24-attention/</li>\n <li>https://github.com/lilianweng/transformer-tensorflow/</li>\n <li>《基于深度学习的道路短期交通状态时空序列预测》崔建勋 著</li>\n <li>https://www.zhihu.com/question/325839123</li>\n <li>https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer</li>\n <li>《Python 深度学习(第 2 版)》弗朗索瓦·肖莱 著</li>\n <li>https://en.wikipedia.org/wiki/Attention_(machine_learning)</li>\n <li>https://zhuanlan.zhihu.com/p/410776234</li>\n <li>https://www.tensorflow.org/tensorboard/get_started</li>\n <li>https://paperswithcode.com/method/multi-head-attention</li>\n <li>https://zhuanlan.zhihu.com/p/48508221</li>\n <li>https://www.joshbelanich.com/self-attention-layer/</li>\n <li>https://learning.rasa.com/transformers/kvq/</li>\n <li>http://deeplearning.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/</li>\n <li>https://zhuanlan.zhihu.com/p/352898810</li>\n <li>https://towardsdatascience.com/beautifully-illustrated-nlp-models-from-rnn-to-transformer-80d69faf2109</li>\n <li>https://medium.com/analytics-vidhya/understanding-q-k-v-in-transformer-self-attention-9a5eddaa5960</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</title>\n \t<meta name=\"description\" content=\"本文译自 LSTM 作者 Jürgen Schmidhuber,全文主要由 AI 翻译生成,麦克船长进行部分校对,这篇超长文章为了串联起了深度学习领域的大事件,以及那些引领我们的优秀科学家们。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】三万字长文!LSTM 之父 Jürgen 带我们回顾深度学习发展史</h2>\t\t\n\t<time datetime=\"2023-01-14T20:21:55+00:00\" class=\"by-line\">14 Jan 2023, 杭州 | Jürgen Schmidhuber | [译] AI & 麦克船长 | 总计 31554 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-1.png\" alt=\"image\" /></p>\n\n<p>本文译自 LSTM 作者 <a href=\"https://people.idsia.ch/~juergen/deep-learning-history.html#gan\">Jürgen Schmidhuber, KAUST AII, Swiss AI Lab IDSIA, USI</a>,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#现代人工智能和深度学习的注释历史\" id=\"markdown-toc-现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</a></li>\n <li><a href=\"#介绍\" id=\"markdown-toc-介绍\">介绍</a></li>\n <li><a href=\"#1676向后信用分配的链式规则\" id=\"markdown-toc-1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</a></li>\n <li><a href=\"#1800第一个神经网络线性回归浅层学习\" id=\"markdown-toc-1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</a></li>\n <li><a href=\"#1920-1925第一个循环网络架构\" id=\"markdown-toc-1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</a> <ul>\n <li><a href=\"#1972首次发布学习人工-rnn\" id=\"markdown-toc-1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</a></li>\n </ul>\n </li>\n <li><a href=\"#1958-年多层前馈神经网络没有深度学习\" id=\"markdown-toc-1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</a></li>\n <li><a href=\"#1965-年第一次深度学习\" id=\"markdown-toc-1965-年第一次深度学习\">1965 年:第一次深度学习</a></li>\n <li><a href=\"#1967-68通过随机梯度下降进行深度学习\" id=\"markdown-toc-1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</a></li>\n <li><a href=\"#1970-年反向传播-1982-年对于神经网络-1960-年先驱\" id=\"markdown-toc-1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</a></li>\n <li><a href=\"#1979-年第一个深度卷积神经网络1969-年relu\" id=\"markdown-toc-1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</a></li>\n <li><a href=\"#1980-年代至-90-年代图形神经网络随机增量规则dropout\" id=\"markdown-toc-1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</a></li>\n <li><a href=\"#1990-年-2-月生成对抗网络好奇心-1\" id=\"markdown-toc-1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</a> <ul>\n <li><a href=\"#1991-年-3-月具有线性化自注意力的变形金刚\" id=\"markdown-toc-1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</a></li>\n </ul>\n </li>\n <li><a href=\"#1991-年-4-月通过自监督预训练进行深度学习\" id=\"markdown-toc-1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</a></li>\n <li><a href=\"#1991-年-6-月基本问题梯度消失\" id=\"markdown-toc-1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</a></li>\n <li><a href=\"#1991-年-6-月lstm--highway-nets--resnets-的根源\" id=\"markdown-toc-1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</a> <ul>\n <li><a href=\"#1995神经概率语言模型\" id=\"markdown-toc-1995神经概率语言模型\">1995:神经概率语言模型</a></li>\n <li><a href=\"#lstm--highway-net-原理是现代深度学习的核心\" id=\"markdown-toc-lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</a></li>\n </ul>\n </li>\n <li><a href=\"#是硬件笨蛋\" id=\"markdown-toc-是硬件笨蛋\">是硬件,笨蛋!</a></li>\n <li><a href=\"#不要忽视-1931-年以来的人工智能理论\" id=\"markdown-toc-不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</a></li>\n <li><a href=\"#从大爆炸到遥远的未来的更广泛的历史背景\" id=\"markdown-toc-从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</a></li>\n</ul>\n\n<h2 id=\"现代人工智能和深度学习的注释历史\">现代人工智能和深度学习的注释历史</h2>\n\n<p><strong>摘要</strong>。 机器学习(ML)是信用分配的科学:在观察中发现预测行动后果的模式,并帮助提高未来的表现。信用分配也是人类理解世界如何运作的必要条件,不仅对于每天生活中的个人,而且对于像历史学家这样的学术专业人士来说也是如此。在这里,我主要关注现代人工智能(AI)的历史,它由人工神经网络(NNs)和深度学习(DL)主导,在概念上更接近早期的控制论,而不是自1956年以来被称为 AI(例如专家系统和逻辑编程)的领域。现代AI的历史重点将强调传统AI教科书以外的突破,特别是当今NNs的数学基础,如链式规则(1676 年),第一个NNs(线性回归,约1800年)和第一个工作的深度学习器(1965-)。从2022年的角度来看,我提供了一个时间表,阐述了NNs,深度学习,AI,计算机科学和数学领域中事后看来最重要的相关事件,并对那些奠定了这一领域基础的人进行了赞扬。文章中包含了许多与我的AI博客相关的概述网站的超链接。它还揭示了深度学习的一些流行但是误导性的历史条目,并补充了我之前的深度学习调查[DL1],其中提供了数百条额外的参考资料。最后,为了结束这篇文章,我将把事情放在更广泛的历史背景中,跨越从大爆炸开始到宇宙将比现在老很多倍的时间。本文也是我即将出版的AI书籍的一章的草稿。</p>\n\n<p><strong>免责声明</strong>。 有人说深度学习的历史不应该由帮助塑造它的人来写——“你是历史的一部分,而不是历史学家。”[CONN21] 我不同意这种观点。 由于我似乎比其他人更了解深度学习的历史,[S20][DL3,DL3a][T22][DL1-2] 我认为记录和推广这些知识是我的责任,即使这似乎暗示着与 兴趣,因为这意味着突出提及我自己团队的工作,因为(截至 2022 年)引用最多的神经网络都是基于它的。[MOST] 未来的人工智能历史学家可能会纠正任何时代特定的潜在偏见。</p>\n\n<h2 id=\"介绍\">介绍</h2>\n\n<p>随着时间的推移,某些历史事件在某些旁观者眼中变得更加重要。 例如,138 亿年前的大爆炸现在被广泛认为是万物历史上的重要时刻。 然而,直到几十年前,地球人还完全不知道它,长期以来,地球人对宇宙的起源抱有相当错误的看法(有关世界历史的更多信息,请参见最后一节)。 目前接受的许多更有限主题的历史是类似激进修订的结果。 在这里,我将重点关注人工智能 (AI) 的历史,它也与过去不同。</p>\n\n<p>1980 年代写的 AI 历史会强调定理证明、[GOD][GOD34][ZU48][NS56] 逻辑编程、专家系统和启发式搜索等主题。[FEI63,83][LEN83] 这将是 与 1956 年达特茅斯会议的主题一致,约翰麦卡锡在会上创造了“人工智能”一词,用来描述一个旧的研究领域重新引起人们的兴趣。 实用 AI 至少可以追溯到 1914 年,当时 Leonardo Torres y Quevedo(见下文)构建了第一个工作的国际象棋终端游戏玩家 [BRU1-4](当时国际象棋被认为是一种仅限于智能生物领域的活动)。 AI 理论至少可以追溯到 1931-34 年,当时 Kurt Gödel(见下文)确定了任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][GOD21,a,b]</p>\n\n<p>2000 年代初期编写的 AI 历史会更加强调支持向量机和内核方法等主题,[SVM1-4] 贝叶斯(实际上是拉普拉斯或可能是桑德森[STI83-85])推理[BAY1-8][ FI22]和其他概率论和统计概念,[MM1-5][NIL98][RUS95]决策树,例如[MIT97]集成方法,[ENS1-4]群体智能,[SW1]和进化计算。<a href=\"[TUR1],未发表\">EVO1 -7</a>为什么? 因为在当时,此类技术推动了许多成功的 AI 应用。</p>\n\n<p>写于 2020 年代的 AI 历史必须强调诸如更古老的链式法则 [LEI07] 和通过梯度下降训练的深度非线性人工神经网络 (NN) [GD’] 等概念,特别是基于反馈的循环网络,它们是 其程序是权重矩阵的通用计算机。[AC90] 为什么? 因为最近许多最著名和最商业化的 AI 应用程序都依赖于它们。[DL4]</p>\n\n<p>这样的 NN 概念实际上在概念上接近 MACY 会议 (1946-1953)[MACY51] 和 1951 年关于计算机器和人类思想的巴黎会议的主题,现在通常被视为关于 AI 的第一次会议。[AI51][BRO21][ BRU4] 然而,在 1956 年之前,现在称为 AI 的大部分内容仍被称为控制论,重点与基于神经网络“深度学习”的现代 AI 非常一致。[DL1-2][DEC]</p>\n\n<p>过去的一些神经网络研究受到人脑的启发,人脑有大约 1000 亿个神经元,每个神经元平均连接到 10,000 个其他神经元。 有些是输入神经元,为其余神经元提供数据(声音、视觉、触觉、疼痛、饥饿)。 其他的是控制肌肉的输出神经元。 大多数神经元隐藏在两者之间,思考发生的地方。 你的大脑显然通过改变连接的强度或权重来学习,这决定了神经元相互影响的强度,并且似乎编码了你一生的所有经历。 与我们的人工 NN 类似,它比以前的方法学习得更好,可以识别语音或手写或视频、最小化痛苦、最大化快乐、驾驶汽车等。[MIR](第 0 节)[DL1-4]</p>\n\n<p>NN 如何学习所有这些? 在下文中,我将强调使这一切成为可能的重要历史贡献。 由于现代 AI 的几乎所有基本概念都源于前几千年,因此下面的章节标题只强调到 2000 年的发展。然而,许多章节都提到了这项工作在新千年的后期影响,这带来了许多 硬件和软件的改进,有点像 20 世纪对 19 世纪发明的汽车进行了大量改进。</p>\n\n<p>本文还揭穿了一个经常重复的、误导性的“深度学习的历史”[S20][DL3,3a],它忽略了下面提到的大部分开创性工作。[T22]见脚注 6。本文的标题图片是一个 对一条错误的常识的反应,该常识说 [T19] 使用神经网络“作为帮助计算机识别模式和模拟人类智能的工具是在 1980 年代引入的”,尽管这种神经网络早在 1980 年代就出现了。 [T22 ] 确保在所有科学中正确分配学分对我来说非常重要——就像对所有科学家一样——我鼓励有兴趣的读者也看看我在《科学》和《自然》杂志上就此发表的一些信件,例如, 关于航空史,[NASC1-2] 电话,[NASC3] 计算机,[NASC4-7] 弹性机器人,[NASC8] 和 19 世纪的科学家。[NASC9]</p>\n\n<p>最后,为了圆满结束,我将把事情放在更广泛的历史背景下,跨越从大爆炸到宇宙比现在古老许多倍的时间。</p>\n\n<h2 id=\"1676向后信用分配的链式规则\">1676:向后信用分配的链式规则</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-2.png\" alt=\"image\" /></p>\n\n<p>莱布尼茨,大约 1670 年的计算机科学之父,于 1676 年发表了链式法则</p>\n\n<p>1676年,戈特弗里德·威廉·莱布尼茨在回忆录中发表了微积分的链式法则(尽管万物皆有符号错误!); Guillaume de l’Hopital 在他 1696 年关于莱布尼茨微积分的教科书中对此进行了描述。[LEI07-10][L84] 今天,这条规则是深度神经网络 (NN) 中信用分配的核心。 为什么? 最流行的 NN 具有计算来自其他神经元的输入的可微函数的节点或神经元,这些节点或神经元又计算来自其他神经元的输入的可微函数,等等。 问题是:如果我们稍微修改早期函数的参数或权重,最终函数的输出将如何变化? 链式法则是计算答案的基本工具。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-3.jpg\" alt=\"image\" /></p>\n\n<p>Cauchy 这个答案被梯度下降 (GD) 技术使用,显然是由 Augustin-Louis Cauchy 于 1847 年首次提出 [GD’](后来由 Jacques Hadamard [GD’’] 提出;称为 SGD 的随机版本归功于 Herbert 罗宾斯和萨顿门罗 (1951)[STO51-52])。 为了教会神经网络将来自训练集的输入模式转换为所需的输出模式,所有神经网络权重都朝着最大局部改进的方向迭代改变一点,以创建稍微更好的神经网络,依此类推,直到获得令人满意的解决方案 成立。</p>\n\n<p>脚注 1. 1684 年,莱布尼茨也是第一个发表“现代”微积分的人;[L84][SON18][MAD05][LEI21,a,b] 后来艾萨克·牛顿也因其未发表的工作而受到赞誉。[SON18] 他们的优先事项 然而,争议 [SON18] 并不包含链式法则。[LEI07-10] 当然,两者都建立在早期工作的基础上:在公元前 2 世纪,阿基米德(也许是有史以来最伟大的科学家 [ARC06])为 无穷小并发表了微积分的特例,例如球体和抛物线段,建立在古希腊更早的工作之上。 14 世纪,Sangamagrama 的 Madhava 和印度喀拉拉邦学派的同事也进行了微积分的基础工作。[MAD86-05]</p>\n\n<p>脚注 2. 值得注意的是,莱布尼茨(1646-1714 年,又名“世界上第一位计算机科学家”[LA14])也奠定了现代计算机科学的基础。 他设计了第一台可以执行所有四种算术运算的机器(1673),以及第一台带有内部存储器的机器。[BL16] 他描述了二进制计算机的原理(1679)[L79][L03][LA14][HO66][ LEI21,a,b] 几乎被所有现代机器所采用。 他的正式思想代数 (1686)[L86][WI48] 与后来的布尔代数 (1847) 演绎等价[LE18]。[BOO] 他的 Characteristica Universalis & Calculus Ratiocinator 旨在通过计算回答所有可能的问题;[WI48 】 他的《微积分! 是启蒙时代的标志性名言之一。 值得注意的是,他还负责链式法则,这是“现代”深度学习的基础,是现代计算机科学的一个重要子领域。</p>\n\n<p>脚注 3. 有人声称反向传播算法(进一步讨论;现在广泛用于训练深度神经网络)只是 Leibniz (1676) & L’Hopital (1696) 的链式法则。[CONN21] 不,这是有效的方法 将链式法则应用于具有可微分节点的大型网络(也有许多低效的方法)。[T22] 直到 1970 年才发布,如下所述。[BP1,4,5]</p>\n\n<h2 id=\"1800第一个神经网络线性回归浅层学习\">~1800:第一个神经网络/线性回归/浅层学习</h2>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的早期未发表的工作而受到赞誉</p>\n\n<p>1805 年,Adrien-Marie Legendre 发表了现在通常称为线性神经网络 (NN) 的内容。 后来,约翰·卡尔·弗里德里希·高斯 (Johann Carl Friedrich Gauss) 也因在大约 1795 年完成的这项未发表的工作而受到赞誉。[STI81]</p>\n\n<p>这个来自 2 个多世纪前的神经网络有两层:一个具有多个输入单元的输入层和一个输出层。 为简单起见,我们假设后者由单个输出单元组成。 每个输入单元都可以保存一个实数值,并通过具有实数值权重的连接连接到输出。 NN 的输出是输入与其权重的乘积之和。 给定输入向量的训练集和每个向量的期望目标值,调整 NN 权重,使 NN 输出与相应目标之间的平方误差之和最小化。</p>\n\n<p>1795 年,高斯使用了现在称为线性神经网络的东西,但勒让德于 1805 年首次发表了它。高斯通常被称为自古以来最伟大的数学家,当然,那时候还不叫神经网络。 它被称为最小二乘法,也被广泛称为线性回归。 但它在数学上与今天的线性神经网络相同:相同的基本算法、相同的误差函数、相同的自适应参数/权重。 这种简单的神经网络执行“浅层学习”(与具有许多非线性层的“深度学习”相反)。 事实上,许多神经网络课程都是从介绍这种方法开始的,然后转向更复杂、更深入的神经网络。</p>\n\n<p>也许第一个通过浅层学习进行模式识别的著名例子可以追溯到 200 多年前:1801 年通过高斯重新发现矮行星谷神星,他从以前的天文观测中获得了数据点,然后使用各种技巧来调整模型的参数 预测器,它基本上学会了从训练数据中进行归纳以正确预测谷神星的新位置。</p>\n\n<p>脚注 4. 今天,所有技术学科的学生都必须上数学课,尤其是分析、线性代数和统计学。 在所有这些领域中,重要的结果和方法(至少部分)归功于高斯:代数基本定理、高斯消去法、统计的高斯分布等。这位号称“自古以来最伟大的数学家”的人也开创了微分 几何、数论(他最喜欢的科目)和非欧几何。 此外,他对天文学和物理学做出了重大贡献。 如果没有他的成果,包括 AI 在内的现代工程将不可想象。</p>\n\n<p>脚注 5. 神经网络的“浅层学习”在 1950 年代后期经历了新一波的流行。 Rosenblatt 的感知器 (1958)[R58] 将上述线性 NN 与输出阈值函数相结合以获得模式分类器(比较他在下面讨论的多层网络上更先进的工作)。 Joseph[R61] 提到了 Farley & Clark 更早的类似感知器的设备。 Widrow & Hoff 的类似 Adaline 在 1962 年学到。[WID62]</p>\n\n<h2 id=\"1920-1925第一个循环网络架构\">1920-1925:第一个循环网络架构</h2>\n\n<p>1924 年,Ernst Ising 发表了第一个循环网络架构:Ising 模型或 Lenz-Ising 模型。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,循环神经网络 (RNN) 具有反馈连接,因此可以遵循从某些内部节点到其他节点的定向连接,并最终在起点处结束。 这对于在序列处理期间实现对过去事件的记忆是必不可少的。</p>\n\n<p>第一个非学习 RNN 架构(Ising 模型或 Lenz-Ising 模型)是由物理学家 Ernst Ising 和 Wilhelm Lenz 在 1920 年代引入和分析的[L20][I24,I25][K41][W45][T22] 它 响应输入条件进入平衡状态,并且是第一个学习 RNN 的基础(见下文)。</p>\n\n<p>非学习 RNN 也在 1943 年由神经科学家 Warren McCulloch 和 Walter Pitts [MC43] 进行了讨论,并在 1956 年由 Stephen Cole Kleene 进行了正式分析。 [K56]</p>\n\n<p>1972 年,Shun-Ichi Amari 使 Ising 递归网络自适应。 这是第一个发表的学习人工递归神经网络</p>\n\n<hr />\n\n<h3 id=\"1972首次发布学习人工-rnn\">~1972:首次发布学习人工 RNN</h3>\n\n<p>1972 年,Shun-Ichi Amari 使 Lenz-Ising 循环架构具有自适应性,这样它就可以通过改变连接权重来学习将输入模式与输出模式相关联。[AMH1] 另见 Stephen Grossberg 关于生物网络的工作,[GRO69] David Marr 的 [MAR71]和Teuvo Kohonen的[KOH72]工作,以及Kaoru Nakano的学习RNN。[NAK72]</p>\n\n<p>艾伦·图灵\n10 年后,Amari 网络被重新发布(并分析了它的存储容量)。[AMH2] 有人称它为 Hopfield 网络(!)或 Amari-Hopfield 网络。[AMH3] 它不处理序列,但在响应中达到平衡 到静态输入模式。 然而,Amari (1972) 也对其进行了序列处理推广[AMH1]</p>\n\n<p>值得注意的是,早在 1948 年,艾伦图灵就提出了与人工进化和学习 RNN 相关的想法。 然而,这在几十年后首次发表,[TUR1] 这解释了他在这里思想的晦涩。[TUR21](边注:有人指出,著名的“图灵测试”实际上应该称为“笛卡尔测试” .[TUR3,a,b][TUR21])</p>\n\n<p>今天最流行的RNN就是下面提到的长短期记忆(LSTM),它已经成为20世纪被引用最多的NN[MOST]</p>\n\n<h2 id=\"1958-年多层前馈神经网络没有深度学习\">1958 年:多层前馈神经网络(没有深度学习)</h2>\n\n<p>1958 年,弗兰克·罗森布拉特 (Frank Rosenblatt) 拥有多层感知器,其最后一层学习</p>\n\n<p>1958 年,Frank Rosenblatt 不仅结合了线性 NN 和阈值函数(参见 1800 年以来的浅层学习部分),他还有更有趣、更深层的多层感知器 (MLP)。[R58] 他的 MLP 有一个非学习的第一层 随机权重和自适应输出层。 虽然这还不是深度学习,因为只有最后一层学习了,[DL1] Rosenblatt 基本上拥有了后来被重新命名为极限学习机 (ELM) 的东西,但没有适当的归因。[ELM1-2][CONN21][T22]</p>\n\n<p>1961 年,Karl Steinbuch [ST61-95] 和 Roger David Joseph [R61] (1961) 也讨论了 MLP。 另见 Oliver Selfridge 的多层 Pandemonium [SE59] (1959)。</p>\n\n<p>Rosenblatt (1962) 甚至写了关于带有隐藏层的 MLP 中的“反向传播错误”[R62],尽管他还没有针对深度 MLP 的通用深度学习算法。 现在称为反向传播的东西完全不同,它于 1970 年首次发布,如下所述。[BP1-BP5][BPA-C]</p>\n\n<p>今天,最流行的 FNN 是基于 LSTM 的 Highway Net(下文提到)的一个版本,称为 ResNet,[HW1-3],它已成为 21 世纪被引用最多的 NN。[MOST]</p>\n\n<h2 id=\"1965-年第一次深度学习\">1965 年:第一次深度学习</h2>\n\n<p>1965 年,Alexey Ivakhnenko 和 Valentin Lapa 推出了第一个适用于具有任意多个隐藏层的深度 MLP 的深度学习算法\n深度前馈网络架构的成功学习始于 1965 年的乌克兰(当时的苏联),当时 Alexey Ivakhnenko 和 Valentin Lapa 为具有任意多个隐藏层(已经包含现在流行的乘法门)的深度 MLP 引入了第一个通用的工作学习算法 .[DEEP1-2][DL1-2][FDL] 1971年的一篇论文[DEEP2]已经描述了一个8层的深度学习网络,用他们被高度引用的方法训练,这种方法在新千年仍然很流行,[DL2]尤其是 在东欧,那里诞生了很多机器学习。[MIR](第 1 节)[R8]</p>\n\n<p>给定一组具有相应目标输出向量的输入向量训练集,层逐渐增长并通过回归分析进行训练,然后借助单独的验证集进行修剪,其中正则化用于清除多余的单元。 层数和每层单元以问题相关的方式学习。</p>\n\n<p>与后来的深度神经网络一样,Ivakhnenko 的网络学会了为传入数据创建分层的、分布式的、内部表示。</p>\n\n<p>他没有称它们为深度学习神经网络,但它们就是这样。 事实上,“深度学习”这个古老的术语最早是由 Dechter (1986) 引入机器学习的,Aizenberg 等人 (2000) 引入神经网络的。[DL2](边注:我们 2005 年关于深度学习的论文 [DL6 ,6a] 是第一本机器学习出版物,标题中包含“深入学习”这个词组合。[T22])</p>\n\n<h2 id=\"1967-68通过随机梯度下降进行深度学习\">1967-68:通过随机梯度下降进行深度学习</h2>\n\n<p>1967-68 年,Shun-Ichi Amari 通过随机梯度下降训练深度 MLP\nIvakhnenko 和 Lapa(1965 年,见上文)逐层训练他们的深层网络。 然而,在 1967 年,Shun-Ichi Amari 建议通过随机梯度下降 (SGD)[GD1] 从头开始以非增量端到端方式训练多层 MLP,这是 Robbins 和 Monro 于 1951 年提出的一种方法。 STO51-52]</p>\n\n<p>Amari 的实现 [GD2,GD2a](与他的学生 Saito)在具有两个可修改层的五层 MLP 中学习了内部表示,该层被训练为对非线性可分离模式类进行分类。 那时候的计算成本是今天的数十亿倍。</p>\n\n<p>另见 Iakov Zalmanovich Tsypkin 更早的关于非线性系统的基于梯度下降的在线学习的工作。[GDa-b]</p>\n\n<p>值得注意的是,如上所述,Amari 还在 1972 年发表了学习 RNN。[AMH1]</p>\n\n<h2 id=\"1970-年反向传播-1982-年对于神经网络-1960-年先驱\">1970 年:反向传播。 1982 年:对于神经网络。 1960 年:先驱。</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-12.png\" alt=\"image\" /></p>\n\n<p>谁发明了反向传播?</p>\n\n<p>1970 年,Seppo Linnainmaa 是第一个发布现在称为反向传播的算法,这是一种著名的可微节点网络信用分配算法,[BP1,4,5] 也称为“自动微分的反向模式”。 它现在是广泛使用的神经网络软件包的基础,例如 PyTorch 和谷歌的 Tensorflow。</p>\n\n<p>1960年,Henry J. Kelley在控制理论领域有了反向传播的先驱\n1982 年,Paul Werbos 在他 1974 年的论文中提出了使用该方法训练神经网络,[BP2] 扩展了思想。</p>\n\n<p>1960 年,Henry J. Kelley 在控制理论领域已经有了反向传播的先驱;[BPA] 另请参阅 Stuart Dreyfus 和 Arthur E. Bryson 在 1960 年代早期的后期工作。[BPB][BPC][R7] 不同于 Linnainmaa 的一般方法,[BP1] 1960 年代的系统[BPA-C] 通过标准雅可比矩阵计算从一个“阶段”到前一个“阶段”反向传播导数信息,既没有解决跨多个阶段的直接链接,也没有解决由于网络导致的潜在额外效率增益 稀疏性。</p>\n\n<p>反向传播本质上是为深度网络实施莱布尼茨链式法则 [LEI07-10] (1676)(见上文)的有效方式。 Cauchy 的梯度下降 [GD’] 使用它在许多试验过程中逐渐削弱某些 NN 连接并加强其他连接,这样 NN 的行为越来越像某个老师,可能是一个人,也可能是另一个 NN,[UN- UN2] 或其他东西。</p>\n\n<p>到 1985 年,计算成本已比 1970 年便宜约 1,000 倍,而第一台台式计算机刚刚在富裕的学术实验室中普及。 David E. Rumelhart 等人对已知方法[BP1-2] 的实验分析。 然后证明反向传播可以在 NN 的隐藏层中产生有用的内部表示。[RUM] 至少对于监督学习,反向传播通常比 Amari 的上述深度学习更有效,通过更一般的 SGD 方法(1967),它学习了有用的内部 大约 2 年前 NN 中的表示。[GD1-2a]</p>\n\n<p>直到 1970 年 [BP1-2] 的反向传播方法被广泛接受作为深度神经网络的训练方法,花了 4 年时间。 在 2010 年之前,许多人认为训练多层神经网络需要无监督预训练,这是我自己在 1991 年提出的方法[UN][UN0-3](见下文),后来得到其他人的支持(2006 年)。[UN4 ] 事实上,据称 [VID1] “没有任何头脑正常的人会建议”将简单的反向传播应用于深度神经网络。 然而,在 2010 年,我们的团队与我出色的罗马尼亚博士后 Dan Ciresan [MLP1-2] 表明,深度 FNN 可以通过简单的反向传播进行训练,并且根本不需要对重要应用进行无监督预训练。 [MLP2]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-14.png\" alt=\"image\" /></p>\n\n<p>我们的系统在当时著名且广泛使用的图像识别基准 MNIST 上创造了新的性能记录 [MLP1]。 这是通过在称为 GPU 的高度并行图形处理单元上极大地加速深度 FNN 来实现的(正如 Jung 和 Oh 在 2004 年 [GPUNN] 首次对层数较少的浅层 NN 所做的那样)。 一位评论家称这是“机器学习社区的警钟”。 今天,该领域的每个人都在追求这种方法。</p>\n\n<p>脚注 6. 不幸的是,在 1980 年代重新发表反向传播的几位作者没有引用现有技术——甚至在后来的调查中也没有。[T22] 事实上,正如引言中提到的,有一个更广泛的、经常重复的、误导性的“ 深度学习的历史[S20]忽略了前面章节中提到的大部分开创性工作。[T22][DLC]这个“替代历史”基本上是这样的:“1969 年,Minsky & Papert[M69] 表明浅 没有隐藏层的神经网络非常有限,该领域被放弃,直到新一代神经网络研究人员在 1980 年代重新审视这个问题。[S20] 然而,1969 年的书 [M69] 解决了高斯的“问题” & Legendre 的浅层学习(大约 1800 年)[DL1-2] 已经在 4 年前被 Ivakhnenko & Lapa 流行的深度学习方法 [DEEP1-2][DL2] 解决了,然后 Amari 的 SGD 也解决了 MLPs。[GD1- 2] Minsky 既没有引用这项工作,也没有在后来更正他的书。<a href=\"Sec. I\">HIN</a>[T22] 甚至 r 最近的论文宣扬了这种对深度学习的修正主义叙述,显然是为了美化其作者后来的贡献(例如玻尔兹曼机[BM][HIN][SK75][G63][T22]),而没有将它们与原始作品联系起来,[DLC ][S20][T22]虽然真实历史众所周知。 深度学习研究在 1960 年代至 70 年代非常活跃,尤其是在英语圈之外。[DEEP1-2][GD1-3][CNN1][DL1-2][T22] 明显的错误归因和无意的[PLAG1][CONN21] 或故意 [FAKE2] 剽窃仍在污染整个深度学习领域。[T22] 科学期刊“需要对自我纠正做出更明确、更坚定的承诺”,[SV20] 这已经是其他科学领域的标准。</p>\n\n<h2 id=\"1979-年第一个深度卷积神经网络1969-年relu\">1979 年:第一个深度卷积神经网络(1969 年:ReLU)</h2>\n\n<p>1979 年,Kunihiko Fukushima 引入了卷积神经网络 (CNN) 架构。计算机视觉在 2010 年代被称为卷积神经网络 (CNN) 的特殊前馈神经网络彻底改变了。[CNN1-4] 具有交替卷积层和下采样层的基本 CNN 架构 这要归功于福岛邦彦 (1979)。 他称之为 Neocognitron。[CNN1]</p>\n\n<p>值得注意的是,早在 10 年前,Fukushima 还为神经网络引入了整流线性单元 (ReLU) (1969)。[RELU1] 它们现在广泛用于 CNN 和其他神经网络。</p>\n\n<p>1987 年,Alex Waibel 将带卷积的神经网络与权重共享和反向传播相结合(见上文),[BP1-2] 并将其应用于语音。[CNN1a] Waibel 没有称此为 CNN,而是 TDNN。</p>\n\n<p>Yamaguchi 等人介绍了一种流行的下采样变体,称为最大池化。 1990 年的 TDNN [CNN3a] 和 Juan Weng 等人。 1993 年用于高维 CNN。[CNN3]</p>\n\n<p>自 1989 年以来,Yann LeCun 的团队为 CNN 的改进做出了贡献,尤其是在图像方面。[CNN2,4][T22] Baldi 和 Chauvin (1993) 首次将具有反向传播功能的 CNN 应用于生物医学/生物特征图像。[BA93]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-16.gif\" alt=\"image\" /></p>\n\n<p>2011 年晚些时候,CNN 在 ML 社区变得更加流行,当时我自己的团队大大加快了深度 CNN 的训练(Dan Ciresan 等人,2011)。[GPUCNN1,3,5] 我们基于 GPU 的快速 [GPUNN][ GPUCNN5] 2011 年的 CNN [GPUCNN1] 被称为 DanNet[DAN,DAN1][R6] 是一个实际的突破,比 2006 年早期的 GPU 加速 CNN 更深更快。[GPUCNN] 2011 年,DanNet 成为第一个纯深度 CNN 赢得计算机视觉竞赛。[GPUCNN2-3,5]</p>\n\n<table>\n <thead>\n <tr>\n <th>Competition[GPUCNN5]</th>\n <th>Date/Deadline</th>\n <th>Image size</th>\n <th>Improvement</th>\n <th>Winner</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>IJCNN 2011 traffic signs</td>\n <td>Aug 06, 2011</td>\n <td>variable</td>\n <td>68.0% (superhuman)</td>\n <td>DanNet[DAN,DAN1]</td>\n </tr>\n <tr>\n <td>ISBI 2012 image segmentation</td>\n <td>Mar 01, 2012</td>\n <td>512x512</td>\n <td>26.1%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ICPR 2012 medical imaging</td>\n <td>Sep 10, 2012</td>\n <td>2048x2048x3</td>\n <td>8.9%</td>\n <td>DanNet[GPUCNN3a]</td>\n </tr>\n <tr>\n <td>ImageNet 2012</td>\n <td>Sep 30, 2012</td>\n <td>256x256x3</td>\n <td>41.4%</td>\n <td>AlexNet[GPUCNN4]</td>\n </tr>\n <tr>\n <td>MICCAI 2013 Grand Challenge</td>\n <td>Sep 08, 2013</td>\n <td>2048x2048x3</td>\n <td>26.5%</td>\n <td>DanNet[GPUCNN8]</td>\n </tr>\n <tr>\n <td>ImageNet 2014</td>\n <td>Aug 18, 2014</td>\n <td>256x256x3</td>\n <td>15.8%</td>\n <td>VGG Net[GPUCNN9]</td>\n </tr>\n <tr>\n <td>ImageNet 2015</td>\n <td>Sep 30, 2015</td>\n <td>256x256x</td>\n <td>315.8%</td>\n <td>ResNet,[HW2] a Highway Net[HW1] with open gates</td>\n </tr>\n </tbody>\n</table>\n\n<p>有一段时间,DanNet 享有垄断地位。 从 2011 年到 2012 年,它赢得了它参加的所有比赛,并连续赢得了四场比赛(2011 年 5 月 15 日、2011 年 8 月 6 日、2012 年 3 月 1 日、2012 年 9 月 10 日)。[GPUCNN5] 特别是在硅谷的 IJCNN 2011 上,DanNet 吹响了比赛,并在国际比赛中取得了第一个超人视觉模式识别[DAN1]。 DanNet 也是第一个获胜的深度 CNN:中国手写比赛(ICDAR 2011)、图像分割比赛(ISBI,2012 年 5 月)、大图像物体检测比赛(ICPR,2012 年 9 月 10 日),以及——在 同一时间——关于癌症检测的医学成像竞赛。[GPUCNN8] 2010 年,我们将 DanNet 介绍给世界上最大的钢铁生产商 Arcelor Mittal,并且能够大大提高钢铁缺陷检测。[ST] 据我所知, 这是重工业的第一个深度学习突破。 2012 年 7 月,我们关于 DanNet[GPUCNN3] 的 CVPR 论文引起了计算机视觉社区的注意。 5 个月后,类似的 GPU 加速 AlexNet 赢得了 ImageNet[IM09] 2012 竞赛。[GPUCNN4-5][R6] 我们的 CNN 图像扫描仪比以前的方法快 1000 倍。[SCAN] 这引起了医疗保健行业的极大兴趣 . 今天,IBM、西门子、谷歌和许多初创公司都在采用这种方法。 VGG 网络(ImageNet 2014 获胜者)[GPUCNN9] 和其他高引用的 CNNs[RCNN1-3] 进一步扩展了 2011 年的 DanNet。[MIR](第 19 节)[MOST]</p>\n\n<p>ResNet,ImageNet 2015 的赢家[HW2](2015 年 12 月)和目前被引用最多的神经网络,[MOST] 是我们早期 Highway Net(2015 年 5 月)的一个版本(开门)[HW1-3][R5] Highway Net(见下文)实际上是我们的 vanilla LSTM(见下文)的前馈网络版本。[LSTM2] 它是第一个有效的、真正具有数百层的深度前馈神经网络(以前的神经网络最多只有几十层) .</p>\n\n<h2 id=\"1980-年代至-90-年代图形神经网络随机增量规则dropout\">1980 年代至 90 年代:图形神经网络/随机增量规则(Dropout)/…</h2>\n\n<p>v.d. 引入了具有快速变化的“快速权重”的神经网络。 Malsburg (1981) 等人。[FAST,a,b] 1987 年由 Pollack [PO87-90] 提出并由 Sperduti、Goller 和 Küchler 扩展/改进的可以操纵图形等结构化数据的深度学习架构 [T22] 在 1990 年代初期。[SP93-97][GOL][KU][T22] 另见我们的图 NN-like, Transformer-like Fast Weight Programmers of 1991[FWP0-1][FWP6][FWP] 学习不断 重写从输入到输出的映射(见下文),以及 Baldi 及其同事的工作。[BA96-03] 如今,图 NN 用于许多应用程序。</p>\n\n<p>Werbos,[BP2][BPTT1] Williams,[BPTT2][CUB0-2]等人[ROB87][BPTT3][DL1]分析了梯度下降的实现方式[GD’][STO51-52][GDa-b][ GD1-2a] 在 RNN 中。 Kohonen 的自组织映射开始流行。[KOH82-89]</p>\n\n<p>80 年代和 90 年代还看到了各种生物学上更合理的深度学习算法的提议,与反向传播不同,这些算法在空间和时间上是局部的。[BB2][NAN1-4][NHE][HEL] 参见概述[MIR](第 15 节) ,第 17 节)以及最近对此类方法重新产生的兴趣。[NAN5][FWPMETA6][HIN22]</p>\n\n<p>1990 年,Hanson 引入了随机增量法则,这是一种通过反向传播训练神经网络的随机方法。 几十年后,这个版本在绰号“dropout”下流行起来。[Drop1-4][GPUCNN4]</p>\n\n<p>1980 年代和 90 年代发表了许多关于 NN(包括 RNN)的其他论文——请参阅 2015 年调查中的大量参考文献。[DL1] 然而,在这里,我们主要将自己限制在——事后看来——最重要的论文,鉴于目前 (短暂的?)2022 年的前景。</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-17.png\" alt=\"image\" /></p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-18.jpg\" alt=\"image\" /></p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h2 id=\"1990-年-2-月生成对抗网络好奇心-1\">1990 年 2 月:生成对抗网络/好奇心</h2>\n\n<p>生成对抗网络 (GAN) 已经变得非常流行。[MOST] 它们于 1990 年首次在慕尼黑以人工好奇心的名义发表。[AC90-20][GAN1] 两个决斗的神经网络(一个概率生成器和一个预测器)正试图 在 minimax 游戏中最大化彼此的损失。[AC](第 1 节)生成器(称为控制器)生成概率输出(使用随机单位 [AC90],就像在后来的 StyleGANs[GAN2] 中一样)。 预测器(称为世界模型)看到控制器的输出并预测环境对它们的反应。 使用梯度下降,预测器 NN 最小化它的错误,而生成器 NN 试图使输出最大化这个错误:一个网络的损失是另一个网络的收益。[AC90](世界模型也可以用于连续在线行动规划。 [AC90][计划 2-3][计划])</p>\n\n<p>1990-91 年以来的人工好奇心和创造力</p>\n\n<p>在 2014 年一篇关于 GAN 的论文之前 4 年,[GAN1] 我著名的 2010 年调查 [AC10] 将 1990 年的生成对抗性神经网络总结如下:“神经网络作为预测世界模型用于最大化控制器的内在奖励,这 与模型的预测误差成正比”(已最小化)。</p>\n\n<p>2014 年的 GAN 就是这样的一个例子,其中试验非常短(就像老虎机问题)并且环境简单地返回 1 或 0,这取决于控制器(或生成器)的输出是否在给定的集合中。[AC20][AC] [T22](第十七节)</p>\n\n<p>其他早期的对抗性机器学习设置 [S59][H90] 非常不同——它们既不涉及无监督神经网络,也不涉及建模数据,也不使用梯度下降。[AC20]</p>\n\n<p>可预测性最小化:无监督极小极大博弈,其中一个神经网络最小化另一个最大化的目标函数</p>\n\n<p>1990 年的原则已被广泛用于强化学习 [SIN5][OUD13][PAT17][BUR18] 和逼真图像合成 [GAN1,2] 的探索,尽管后者最近被 Rombach 等人接管。 s Latent Diffusion,另一种在慕尼黑发表的方法,[DIF1] 建立在 Jarzynski 上个千年的早期物理学工作 [DIF2] 和最近的论文的基础上。 [DIF3-5]</p>\n\n<p>1991 年,我发布了另一种基于两个称为可预测性最小化的对抗性神经网络的 ML 方法,用于创建部分冗余数据的分离表示,并于 1996 年应用于图像。[PM0-2][AC20][R2][MIR](第 7 节) )</p>\n\n<h3 id=\"1991-年-3-月具有线性化自注意力的变形金刚\">1991 年 3 月:具有线性化自注意力的变形金刚</h3>\n\n<p>最近,Transformers[TR1] 风靡一时,例如生成听起来像人类的文本。[GPT3] Transformers with “linearized self-attention”[TR5-6] 发表于 1991 年 3 月[FWP0-1][FWP6] [FWP](除了正常化——见 2022 年 30 周年推文)。 这些所谓的“Fast Weight Programmers”或“Fast Weight Controllers”[FWP0-1] 像传统计算机一样将存储和控制分开,但是以端到端可微分的、自适应的、完全神经的方式(而不是 混合时尚[PDA1-2][DNC])。 标准变形金刚 [TR1-4] 中的“自注意力”将其与投影和 softmax 相结合(使用像我在 1993 年 [ATT][FWP2][R4] 中介绍的那样的注意力术语)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-19.png\" alt=\"image\" />\n1991 年 3 月 26 日:神经网络学习使用快速权重对神经网络进行编程——就像今天的 Transformer 变体一样。 2021 年:新东西!</p>\n\n<p>今天的变形金刚大量使用无监督预训练[UN0-3](见下一节),这是另一种深度学习方法,首次发表于我们 1990-1991 年的奇迹年[MIR][MOST]</p>\n\n<p>1991 年的快速权重程序员还导致了元学习自参照神经网络,它们可以在自己身上运行自己的权重变化算法或学习算法,并对其进行改进,并改进它们改进它的方式,等等。 这项工作自 1992 年以来[FWPMETA1-9][HO1] 扩展了我 1987 年的毕业论文,[META1] 介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法。 这在 2010 年代[DEC] 变得非常流行,当时计算机的速度快了一百万倍。</p>\n\n<h2 id=\"1991-年-4-月通过自监督预训练进行深度学习\">1991 年 4 月:通过自监督预训练进行深度学习</h2>\n\n<p>今天最强大的 NN 往往非常深,也就是说,它们有很多层神经元或许多后续计算阶段。[MIR] 然而,在 1990 年代之前,基于梯度的训练对深度 NN 效果不佳,仅适用于浅层 NN [DL1-2](但请参阅 1989 年的一篇论文 [MOZ])。 这个深度学习问题对于循环神经网络最为明显。 与人脑相似,但与更有限的前馈神经网络 (FNN) 不同,RNN 具有反馈连接。 这使得 RNN 成为功能强大的通用并行顺序计算机,可以处理任意长度的输入序列(想想语音数据或视频)。 RNN 原则上可以实现任何可以在您的笔记本电脑或任何其他现有计算机上运行的程序。 如果我们想要构建通用人工智能 (AGI),那么它的底层计算基础必须更像 RNN 而不是 FNN,因为 FNN 从根本上是不够的; RNN 和类似系统之于 FNN 就像通用计算机之于袖珍计算器一样。 特别是,与 FNN 不同,RNN 原则上可以处理任意深度的问题。[DL1] 然而,在 1990 年代之前,RNN 在实践中未能学习深度问题。[MIR](第 0 节)</p>\n\n<p>为了通过基于 RNN 的“一般深度学习”克服这个缺点,我构建了一个自我监督的 RNN 层次结构,它在多个抽象层次和多个自组织时间尺度上学习表示:[LEC] 神经序列分块器 [UN0] 或神经网络 History Compressor.[UN1] 每个 RNN 都试图解决预测其下一个输入的借口任务,仅将意外输入(因此也是目标)发送到上面的下一个 RNN。 由此产生的压缩序列表示极大地促进了下游监督深度学习,例如序列分类。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-20.png\" alt=\"image\" /></p>\n\n<p>尽管当时的计算机每一美元的计算速度比今天慢了大约一百万倍,但到 1993 年,上面的神经历史压缩器已经能够解决以前无法解决的深度 > 1000[UN2](需要超过 1,000 个后续计算阶段)的“非常深度学习”任务 ——这样的阶段越多,学习越深入)。 1993 年,我们还发布了神经历史压缩器的连续版本。[UN3](另请参阅最近关于无监督的基于神经网络的抽象的工作。[OBJ1-5])</p>\n\n<p>这项工作十多年后,[UN1] 发布了一种类似的用于更有限的前馈神经网络 (FNN) 的无监督方法,通过对称为深度信念网络 (DBN) 的 FNN 堆栈进行无监督预训练来促进监督学习。[UN4] 2006 年的理由基本上是我在 1990 年代初期为我的 RNN 堆栈使用的理由:每个更高级别都试图减少下面级别中数据表示的描述长度(或负对数概率)。[HIN][T22][MIR]</p>\n\n<p>1991 年 4 月:将一个 NN 提炼成另一个 NN\n使用我 1991 年的 NN 蒸馏程序,可以将上述神经历史压缩器的分层内部表示折叠成单个循环神经网络 (RNN)。[UN0-1][MIR] 在这里,教师神经网络的知识被“蒸馏”成 一个学生 NN,通过训练学生 NN 模仿老师 NN 的行为(同时还对学生 NN 重新训练以前学过的技能,这样它就不会忘记它们)。 NN 蒸馏也在多年后重新发表,[DIST2][MIR][HIN][T22] 并在今天被广泛使用。</p>\n\n<p>如今,无监督预训练被 Transformers[TR1-6] 大量用于自然语言处理和其他领域。 值得注意的是,具有线性化自注意力的 Transformers 也首次在我们的 Annus Mirabilis of 1990-1991 中发表[FWP0-6],[MIR][MOST] 以及用于深度学习的无监督/自监督预训练。[UN0-3 ] 见上一节。</p>\n\n<h2 id=\"1991-年-6-月基本问题梯度消失\">1991 年 6 月:基本问题:梯度消失</h2>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-21.jpg\" alt=\"image\" /></p>\n\n<p>Sepp Hochreiter 对基本深度学习问题的分析 (1991) 深度学习之所以困难,是因为我的第一个学生 Sepp Hochreiter 在他的毕业论文中于 1991 年确定并分析了基本深度学习问题,我很高兴能够监督。[VAN1] 首先 他实现了上面的神经历史压缩器,但后来做了更多:他表明深度神经网络存在现在著名的梯度消失或爆炸问题:在典型的深度或循环网络中,反向传播的误差信号要么迅速缩小,要么从中消失 界限。 在这两种情况下,学习都会失败(比较[VAN2])。 这种分析导致了现在称为 LSTM 的基本原理(见下文)。</p>\n\n<h2 id=\"1991-年-6-月lstm--highway-nets--resnets-的根源\">1991 年 6 月:LSTM / Highway Nets / ResNets 的根源</h2>\n\n<p>长短期记忆 (LSTM) 循环神经网络 [LSTM1-6] 克服了 Sepp 在其上述 1991 年毕业论文 [VAN1] 中确定的基本深度学习问题,我认为这是历史上最重要的文献之一 机器学习。 它还通过我们在 1995 年的一份技术报告中称为 LSTM 的基本原理(例如恒定错误流)提供了解决问题的重要见解。[LSTM0] 在 1997 年主要同行评审出版物之后 [LSTM1][25y97] (现在是 20 世纪被引用次数最多的 NN 文章 [MOST]),LSTM 及其训练程序在我在 IDSIA 的瑞士 LSTM 资助下通过我后来的学生 Felix Gers、Alex Graves 和其他人的工作得到了进一步改进。 一个里程碑是带有遗忘门 [LSTM2] 的“香草 LSTM 架构”——今天每个人都在使用的 1999-2000 的 LSTM 变体,例如在谷歌的 Tensorflow 中。 Alex 是我们首次将 LSTM 成功应用于语音(2004 年)的主要作者。[LSTM10] 2005 年首次发布了具有全时间反向传播的 LSTM 和双向 LSTM[LSTM3](现已广泛使用)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-22.gif\" alt=\"image\" /></p>\n\n<p>2006 年的另一个里程碑是用于同时比对和识别序列的训练方法“Connectionist Temporal Classification”或 CTC[CTC]。 我们的团队在 2007 年成功地将 CTC 训练的 LSTM 应用于语音 [LSTM4](也使用分层 LSTM 堆栈 [LSTM14])。 这导致了第一个卓越的端到端神经语音识别。 它与 20 世纪 80 年代末以来将神经网络与传统方法(如隐马尔可夫模型 (HMM))相结合的混合方法有很大不同。[BW][BRI][BOU][HYB12][T22] 在 2009 年,通过 Alex 的努力, CTC训练的LSTM成为第一个赢得国际比赛的RNN,即三项ICDAR 2009 Connected Handwriting Competitions(法语,波斯语,阿拉伯语)。 这引起了业界的极大兴趣。 LSTM 很快被用于涉及序列数据的所有事物,例如语音 [LSTM10-11][LSTM4][DL1] 和视频。 2015 年,CTC-LSTM 组合显着改善了谷歌在 Android 智能手机上的语音识别。[GSR15] 许多其他公司采用了这一点。[DL4] 谷歌 2019 年新的设备语音识别(现在在你的手机上,而不是在服务器上) 仍然是基于LSTM。</p>\n\n<h3 id=\"1995神经概率语言模型\">1995:神经概率语言模型</h3>\n\n<p>第一个出色的端到端神经机器翻译也是基于 LSTM。 1995 年,我们已经有了一个优秀的神经概率文本模型 [SNT],其基本概念在 2003 年 [NPM][T22] 中得到了重用——另请参阅 Pollack 早期关于词嵌入和其他结构的工作 [PO87][PO90] 以及 Nakamura 和 Shikano 1989 年的词类别预测模型。[NPMa] 2001 年,我们表明 LSTM 可以学习 HMM 等传统模型无法学习的语言,[LSTM13] 即神经“亚符号”模型突然擅长学习“符号”任务。 计算仍然必须便宜 1000 倍,但到 2016 年,谷歌翻译 [GT16]——其白皮书 [WU] 提到 LSTM 超过 50 次——基于两个连接的 LSTM,[S2S] 一个用于传入文本,一个用于传出翻译 - 比以前好得多。[DL4] 到 2017 年,LSTM 还支持 Facebook 的机器翻译(每周超过 300 亿次翻译——最受欢迎的 YouTube 视频需要数年时间才能实现仅 100 亿次点击),[FB17][DL4] 苹果的 大约 10 亿部 iPhone 上的 Quicktype,[DL4] 亚马逊 Alexa 的声音,[DL4] 谷歌的图像标题生成 [DL4] 和自动电子邮件回复 [DL4] 等。《商业周刊》称 LSTM “可以说是最商业化的 AI 成就”。[AV1 ] 到 2016 年,谷歌数据中心超过四分之一的强大推理计算能力用于 LSTM(5% 用于另一种流行的深度学习技术,称为 CNN——见上文)。[JOU17] 当然,我们的 LSTM 也是 大量用于医疗保健和医疗诊断——一个简单的谷歌 e Scholar search 出现了无数标题中带有“LSTM”的医学文章。[DEC]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-23.png\" alt=\"image\" /></p>\n\n<p>通过我的学生 Rupesh Kumar Srivastava 和 Klaus Greff 的工作,LSTM 原理也促成了我们 2015 年 5 月的 Highway Network[HW1],这是第一个具有数百层的非常深的 FNN(以前的 NN 最多只有几十层) ). Microsoft 的 ResNet[HW2](赢得了 ImageNet 2015 竞赛)是其一个版本(ResNet 是大门始终敞开的 Highway Net)。 早期的 Highway Nets 在 ImageNet 上的表现与它们的 ResNet 版本大致相同。[HW3] highway gates 的变体也用于某些算法任务,其中纯残差层不能很好地工作。[NDR]</p>\n\n<h3 id=\"lstm--highway-net-原理是现代深度学习的核心\">LSTM / Highway Net 原理是现代深度学习的核心</h3>\n\n<p>深度学习完全是关于神经网络深度的。[DL1] 在 1990 年代,LSTM 为受监督的循环神经网络带来了本质上无限的深度; 在 2000 年代,受 LSTM 启发的 Highway Nets 将其引入前馈神经网络。 LSTM 已成为 20 世纪引用最多的神经网络; 名为 ResNet 的 Highway Net 版本是 21 世纪引用最多的神经网络。[MOST](然而,引用是衡量真实影响的一个非常值得怀疑的衡量标准。[NAT1])</p>\n\n<p>1980s-:在没有老师的情况下学习行动的神经网络\n前面的部分主要关注用于被动模式识别/分类的深度学习。 然而,NN 也与强化学习 (RL)、[KAE96][BER96][TD3][UNI][GM3][LSTMPG] 最通用的学习类型相关。 一般的 RL 智能体必须在没有教师帮助的情况下发现如何与动态的、最初未知的、部分可观察的环境进行交互,以最大化其预期的累积奖励信号。[DL1] 动作之间可能存在任意的、先验未知的延迟 和可察觉的后果。 RL 问题与计算机科学的任何问题一样困难,因为任何具有可计算描述的任务都可以在通用 RL 框架中制定。[UNI]</p>\n\n<p>某些强化学习问题可以通过 80 年代之前发明的非神经技术来解决:蒙特卡洛(树)搜索(MC,1949 年)、[MOC1-5] 动态规划(DP,1953 年)、[BEL53] 人工进化(1954 年) ,<a href=\"[TUR1],未发表\">EVO1-7</a> alpha-beta-pruning (1959),[S59] 控制理论与系统辨识 (1950s),[KAL59][GLA85] 随机梯度下降 (SGD, 1951),[ STO51-52]和通用搜索技术(1973)。[AIT7]</p>\n\n<p>然而,深度 FNN 和 RNN 是改进某些类型的 RL 的有用工具。 在 1980 年代,函数逼近和 NN 的概念与系统识别相结合,[WER87-89][MUN87][NGU89] DP 及其在线变体 Temporal Differences (TD),[TD1-3] 人工进化,[EVONN1- 3] 和策略梯度。[GD1][PG1-3] 可以在第 1 节中找到有关此的许多其他参考资料。 2015 年调查的 6 [DL1]</p>\n\n<p>当环境存在马尔可夫接口 [PLAN3],使得 RL 机器的当前输入传达了确定下一个最佳动作所需的所有信息时,基于 DP/TD/MC 的 FNN 的 RL 可以非常成功,如图所示 1994 年 [TD2](大师级西洋双陆棋玩家)和 2010 年代 [DM1-2a](围棋、国际象棋和其他游戏的超人玩家)。</p>\n\n<p>对于没有马尔可夫接口的更复杂的情况,学习机不仅要考虑当前输入,还要考虑以前输入的历史,我们的 RL 算法和 LSTM[LSTM-RL][RPG] 的组合已经成为标准,特别是, 我们的 LSTM 通过策略梯度训练 (2007).[RPG07][RPG][LSTMPG]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-24.png\" alt=\"image\" /></p>\n\n<p>例如,2018 年,PG 训练的 LSTM 是 OpenAI 著名的 Dactyl 的核心,它学会了在没有老师的情况下控制灵巧的机器人手。[OAI1][OAI1a] 视频游戏类似:2019 年,DeepMind(由 我实验室的一名学生)在星际争霸游戏中击败了一名职业玩家,这在理论上比国际象棋或围棋 [DM2] 在许多方面都更难,使用的是 Alphastar,其大脑具有由 PG 训练的深层 LSTM 核心。[DM3] 强化学习 LSTM(占模型总参数数的 84%)也是著名的 OpenAI Five 的核心,它学会了在 Dota 2 视频游戏(2018 年)中击败人类专家。[OAI2] 比尔·盖茨称这是“进步的巨大里程碑” 人工智能”。[OAI2a][MIR](第 4 节)[LSTMPG]</p>\n\n<p>RL 的未来将是关于使用复杂输入流的紧凑时空抽象进行学习/组合/规划——关于常识推理[MAR15] 和学习思考。[PLAN4-5] 分层方式,在多个抽象级别和多个时间尺度?[LEC] 我们在 1990-91 年发表了这些问题的答案:自我监督的神经历史压缩器 [UN][UN0-3] learn to represent percepts at multiple levels 抽象和多个时间尺度(见上文),而端到端可区分的基于神经网络的子目标生成器[HRL3] [MIR](第 10 节)通过梯度下降学习分层行动计划(见上文)。 更复杂的学习抽象思考的方法发表于 1997[AC97][AC99][AC02] 和 2015-18.[PLAN4-5]</p>\n\n<h2 id=\"是硬件笨蛋\">是硬件,笨蛋!</h2>\n\n<p>如果没有不断改进和加速计算机硬件,深度学习算法在过去千年中的最新突破(见前几节)是不可能的。 如果不提及这种已经运行了至少两千年的进化,任何人工智能和深度学习的历史都是不完整的。</p>\n\n<p>第一个已知的基于齿轮的计算设备是 2000 多年前古希腊的 Antikythera 机制(一种天文钟)。</p>\n\n<p>也许世界上第一台实用的可编程机器是 1 世纪 [SHA7a][RAU1] 由亚历山大的赫伦制造的自动剧院(显然他还拥有第一台已知的工作蒸汽机 - Aeolipile)。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-25.png\" alt=\"image\" /></p>\n\n<p>Banu Musa 兄弟在 9 世纪在巴格达制造的音乐自动机可能是第一台具有存储程序的机器。[BAN][KOE1] 它使用旋转圆柱体上的销来存储控制蒸汽驱动长笛的程序——比较 Al-Jazari 的可编程 1206.[SHA7b] 的鼓机</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-26.png\" alt=\"image\" /></p>\n\n<p>1600 年代带来了更灵活的机器,可以根据输入数据计算答案。 第一个用于简单算术的基于数据处理齿轮的专用计算器是由 Wilhelm Schickard 于 1623 年建造的,Wilhelm Schickard 是“自动计算之父”称号的候选人之一,其次是 Blaise Pascal 的高级 Pascaline(1642 年)。 1673 年,已经提到的戈特弗里德·威廉·莱布尼茨(被称为“有史以来最聪明的人”[SMO13])设计了第一台可以执行所有四种算术运算的机器(计步器),并且是第一台带有记忆的机器。[BL16] 他还描述了由穿孔卡 (1679)、[L79][L03][LA14][HO66] 控制的二进制计算机的原理,并发表了链式法则[LEI07-10](见上文),深度学习和现代的基本要素 人工智能。</p>\n\n<p>大约 1800 年,约瑟夫-玛丽·雅卡尔 (Joseph-Marie Jacquard) 和其他人在法国制造了第一台商业程序控制机器(基于打孔卡的织机)——他们可能是编写世界上第一个工业软件的第一批“现代”程序员。 他们启发了 Ada Lovelace 和她的导师 Charles Babbage(英国,大约 1840 年)。 他计划但无法构建一台可编程的通用计算机(只有他的非通用专用计算器导致了 20 世纪的工作复制品)。</p>\n\n<p>Leonardo Torres y Quevedo,20 世纪第一个实用 AI 的先驱 1914 年,西班牙人 Leonardo Torres y Quevedo(在介绍中提到)成为 20 世纪第一个 AI 先驱,他创造了第一个工作的国际象棋终端玩家(当时国际象棋被认为 作为一种仅限于智能生物领域的活动)。 几十年后,当另一位 AI 先驱 Norbert Wiener [WI48] 在 1951 年巴黎 AI 会议上与它对战时,这台机器仍然被认为令人印象深刻。 [AI51][BRO21][BRU4]</p>\n\n<p>1935 年至 1941 年间,Konrad Zuse 创造了世界上第一台可运行的可编程通用计算机:Z3。 1936 年的相应专利 [ZU36-38][RO98][ZUS21] 描述了可编程物理硬件所需的数字电路,早于克劳德香农 1937 年关于数字电路设计的论文。[SHA37] 与巴贝奇不同,Zuse 使用了莱布尼茨的二进制计算原理 (1679)[L79][LA14][HO66][L03] 代替传统的十进制计算。 这极大地简化了硬件。[LEI21,a,b] 忽略任何物理计算机不可避免的存储限制,Z3 的物理硬件在哥德尔 [GOD][GOD34, GOD34, 21,21a] (1931-34)、Church[CHU] (1935)、Turing[TUR] (1936) 和 Post[POS] (1936)。 简单的算术技巧可以弥补 Z3 缺少明确的条件跳转指令。[RO98] 今天,大多数计算机都像 Z3 一样是二进制的。</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-28.png\" alt=\"image\" /></p>\n\n<p>Z3 使用带有明显移动开关的电磁继电器。 第一个电子专用计算器(其运动部件是电子,太小以至于看不见)是约翰·阿塔纳索夫(“管基计算之父”[NASC6a])的二进制 ABC(美国,1942 年)。 与 1600 年代基于齿轮的机器不同,ABC 使用真空管——今天的机器使用 Julius Edgar Lilienfeld 于 1925 年获得专利的晶体管原理。[LIL1-2] 但与 Zuse 的 Z3 不同,ABC 不能自由编程。 Tommy Flowers(英国,1943-45 年)的电子巨像机器也没有用来破解纳粹密码。[NASC6]</p>\n\n<p>由 Zuse (1941)[RO98] 以外的人建造的第一台通用工作可编程机器是 Howard Aiken 的十进制 MARK I(美国,1944 年)。 由 Eckert 和 Mauchly (1945/46) 开发的速度快得多的十进制 ENIAC 是通过重新布线来编程的。 数据和程序都被“曼彻斯特宝贝”(Williams, Kilburn & Tootill, UK, 1948)和 1948 年升级的 ENIAC 存储在电子存储器中,通过将数字指令代码输入只读存储器来重新编程。 [HAI14b]</p>\n\n<p>从那时起,计算机通过集成电路 (IC) 变得更快。 1949 年,西门子的 Werner Jacobi 为在公共基板上具有多个晶体管的 IC 半导体申请了专利(于 1952 年授予)。[IC49-14] 1958 年,Jack Kilby 展示了带有外部导线的 IC。 1959 年,罗伯特·诺伊斯 (Robert Noyce) 提出了单片 IC。[IC14] 自 1970 年代以来,图形处理单元 (GPU) 已被用于通过并行处理来加速计算。 今天(2022 年)的 IC/GPU 包含数十亿个晶体管(几乎所有晶体管都是 Lilienfeld 的 1925 FET 类型[LIL1-2])。</p>\n\n<p>1941 年,Zuse 的 Z3 每秒可以执行大约一个基本运算(例如加法)。 从那时起,每 5 年,计算成本就会降低 10 倍(请注意,他的定律比摩尔定律要古老得多,摩尔定律指出每个芯片的晶体管 [LIL1-2] 数量每 18 个月翻一番)。 截至 2021 年,即 Z3 之后的 80 年,现代计算机每秒可以以相同(经通货膨胀调整后)的价格执行约 1000 万亿条指令。 对这种指数趋势的天真推断预测,21 世纪将出现廉价计算机,其原始计算能力是所有人类大脑总和的一千倍。[RAW]</p>\n\n<p>物理极限在哪里? 根据 Bremermann (1982),[BRE] 一台质量为 1 千克和体积为 1 升的计算机最多可以在最多 1032 位上每秒执行最多 1051 次操作。 上述趋势将在 Z3 之后大约 25 年,即 2200 年左右达到布雷默曼极限。但是,由于太阳系中只有 2 x 1030 千克的质量,因此趋势势必会在几个世纪内打破,因为光速 将极大地限制额外质量的获取,例如,以其他太阳系的形式,通过及时的函数多项式,如先前在 2004 年指出的那样。[OOPS2][ZUS21]</p>\n\n<p>物理学似乎要求未来高效的计算硬件必须像大脑一样,在 3 维空间中有许多紧凑放置的处理器,由许多短线和少量长线稀疏地连接,以最小化总连接成本(即使“线” 实际上是光束)。[DL2] 基本架构本质上是一种深度的、稀疏连接的 3 维 RNN,这种 RNN 的深度学习方法有望变得比今天更加重要。[DL2 ]</p>\n\n<h2 id=\"不要忽视-1931-年以来的人工智能理论\">不要忽视 1931 年以来的人工智能理论</h2>\n\n<p>现代人工智能和深度学习的核心主要基于近几个世纪的简单数学:微积分/线性代数/统计学。 然而,要在上一节中提到的现代硬件上有效地实现这个核心,并为数十亿人推出它,需要大量的软件工程,基于上个世纪发明的大量智能算法。 这里没有余地一一提及。 然而,至少我会列出人工智能和计算机科学理论的一些最重要的亮点。</p>\n\n<p>1930 年代初期,哥德尔创立了现代理论计算机科学。[GOD][GOD34][LEI21,21a] 他介绍了一种通用编码语言 (1931-34)。[GOD][GOD34-21a] 它基于整数, 并允许以公理形式形式化任何数字计算机的操作。 哥德尔用它来表示数据(例如公理和定理)和程序 [VAR13](例如数据操作的证明生成序列)。 他著名地构造了关于其他形式陈述的计算的形式陈述——特别是暗示它们不可判定的自引用陈述,给定一个计算定理证明器,系统地从一组可枚举的公理中列举所有可能的定理。 因此,他确定了算法定理证明、计算和任何类型的基于计算的 AI 的基本限制。[GOD][BIB3][MIR](第 18 节)[GOD21,21a]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-29.png\" alt=\"image\" /></p>\n\n<p>像大多数伟大的科学家一样,哥德尔建立在早期工作的基础上。 他将 Georg Cantor 的对角化技巧 [CAN](在 1891 年表明存在不同类型的无穷大)与 Gottlob Frege [FRE](他在 1879 年引入了第一种形式语言)、Thoralf Skolem [SKO23](他 在 1923 年引入了原始递归函数)和 Jacques Herbrand [GOD86](他发现了 Skolem 方法的局限性)。 这些作者又建立在 Gottfried Wilhelm Leibniz[L86][WI48](见上文)的正式思想代数(1686 年)的基础上,它与后来的 1847 年布尔代数演绎等价[LE18]。[BOO]</p>\n\n<p>1935 年,Alonzo Church 通过证明 Hilbert & Ackermann 的 Entscheidungsproblem(决策问题)没有一般解决方案,得出了哥德尔结果的推论/扩展。[CHU] 为此,他使用了他的替代通用编码语言,称为 Untyped Lambda Calculus, 它构成了极具影响力的编程语言 LISP 的基础。 1936年,Alan M. Turing引入了另一个通用模型:图灵机。[TUR]他重新推导了上述结果。[CHU][TUR][HIN][GOD21,21a][TUR21][LEI21,21a] 在 1936 年的同一年,Emil Post 发表了另一个独立的通用计算模型。[POS] 今天我们知道很多这样的模型。</p>\n\n<p>Konrad Zuse 不仅创造了世界上第一台可工作的可编程通用计算机,[ZU36-38][RO98][ZUS21],他还设计了第一种高级编程语言 Plankalkül。[BAU][KNU],他将其应用于国际象棋 在 1945 年 [KNU] 和 1948 年的定理证明。[ZU48] 比较 Newell 和 Simon 在定理证明方面的后期工作(1956)。[NS56] 1940 年代至 70 年代的许多早期人工智能实际上是关于哥德尔风格的定理证明和演绎 [GOD][GOD34,21,21a] 通过专家系统和逻辑编程。</p>\n\n<p>1964 年,Ray Solomonoff 将贝叶斯(实际上是拉普拉斯[STI83-85])概率推理与理论计算机科学[GOD][CHU][TUR][POS] 相结合,推导出一种数学上最优(但计算上不可行)的学习预测未来的方式 [AIT1][AIT10] 与 Andrej Kolmogorov 一起创立了 Kolmogorov 复杂性理论或算法信息论 (AIT),[AIT1-22] 通过形式化概念超越了传统信息论 [SHA48][KUL] 奥卡姆剃刀原理,通过计算数据的最短程序的概念,支持对给定数据进行最简单的解释。 这个概念有许多可计算的、有时间限制的版本,[AIT7][AIT5][AIT12-13][AIT16-17] 以及神经网络的应用。[KO2][CO1-3]</p>\n\n<p>在 2000 年代初期,Marcus Hutter(在我的瑞士国家科学基金会资助 [UNI] 下工作时)通过最佳动作选择器(通用 AI)增强了 Solomonoff 的通用预测器 [AIT1][AIT10],用于强化学习代理,这些代理最初未知( 但至少是可计算的)环境。[AIT20,22] 他还推导出了所有明确定义的计算问题的渐近最快算法,[AIT21] 解决任何问题的速度与此类问题的未知最快求解器一样快,除了加法常数 不依赖于问题的大小。</p>\n\n<p>自参考 2003 哥德尔机 [GM3-9] 的更一般的最优性不限于渐近最优性。</p>\n\n<p>然而,由于各种原因,这种数学上最优的 AI 在实践中尚不可行。 相反,实用的现代 AI 是基于次优的、有限的,但还不是很容易理解的技术,例如神经网络和深度学习,这是本文的重点。 但谁知道 20 年后会出现什么样的 AI 历史呢?</p>\n\n<h2 id=\"从大爆炸到遥远的未来的更广泛的历史背景\">从大爆炸到遥远的未来的更广泛的历史背景</h2>\n\n<p>信用分配是关于在历史数据中寻找模式,并弄清楚以前的事件是如何促成某些事件的。 历史学家这样做。 物理学家这样做。 AI 也会这样做。 让我们退后一步,在最广泛的历史背景下审视人工智能:自大爆炸以来的所有时间。 2014 年,我在其中发现了一个美丽的指数加速模式,[OMG] 从那以后我在许多演讲中都提出了它,它也被写进了 Sibylle Berg 的获奖书籍“GRM:Brainfuck”。[OMG2] 以前出版 这种模式跨越的时间间隔要短得多:只有几十年或几个世纪或最多几千年。[OMG1]</p>\n\n<p>事实证明,从人类的角度来看,自宇宙诞生以来最重要的事件都整齐地排列在指数加速的时间轴上(误差线大多低于 10%)。 事实上,历史似乎在 2040 年左右汇聚在一个欧米茄点。 我喜欢叫它Omega,因为一个世纪前,Teilhard de Chardin称Omega是人类将达到下一个层次的点。[OMG0]另外,Omega听起来比“Singularity”[SING1-2]好听多了——听起来有点 就像“哦,我的上帝。”[OMG]</p>\n\n<p>让我们从138亿年前的大爆炸说起。 我们将这个时间除以 4 得到大约 35 亿年。 欧米茄是2040年左右。 在欧米茄负 35 亿年时,发生了一件非常重要的事情:生命出现在这个星球上。</p>\n\n<p>我们再次花费四分之一的时间。 我们在 9 亿年前出现,当时发生了一件非常重要的事情:类似动物的移动生命出现了。</p>\n\n<p>我们再除以 4。我们在 2.2 亿年前,当哺乳动物被发明时,我们就出来了,我们的祖先。</p>\n\n<p>我们再次除以 4。5500 万年前,第一批灵长类动物出现了,我们的祖先。</p>\n\n<p>自宇宙诞生以来最重要的事件似乎整齐地排列在 2040 年左右收敛于 Omega 点的指数加速时间线上(J Schmidhuber,2014 年)</p>\n\n<p>我们再次除以 4。1300 万年前,第一批原始人出现了,我们的祖先。 我不知道为什么所有这些除以 4 的除法总是在历史上出现这些决定性的时刻。 但他们确实如此。 我也试过三度、五度和谐波比例,但似乎只有四分之一奏效。</p>\n\n<p>我们再次除以 4。350 万年前发生了一件非常重要的事情:技术的黎明,正如大自然所说:第一批石器。</p>\n\n<p>我们除以 4。80 万年前,下一个伟大的技术突破发生了:可控火力。</p>\n\n<p>我们除以 4。 20 万年前,解剖学上的现代人变得突出,我们的祖先。</p>\n\n<p>我们除以 4. 5 万年前,出现了行为上现代的人,我们的祖先,并开始在世界上殖民。</p>\n\n<p>我们再次除以 4。我们在 13000 年前出现,当时发生了一件非常重要的事情:动物的驯化、农业、第一批定居点——文明的开始。 现在我们看到,所有的文明只是世界历史上的一瞬间,只是大爆炸以来时间的百万分之一。 农业和航天器几乎是同时发明的。</p>\n\n<p>我们除以 4。 3300 年前,铁器时代出现了第一次人口爆炸。</p>\n\n<p>我们除以 4。请记住,收敛点 Omega 是 2040 年左右。 欧米茄负 800 年——那是在 13 世纪,在中国,铁和火以枪炮、大炮和火箭的形式结合在一起。 从那时起,这就定义了世界,西方仍然远远落后于欠中国的许可费。</p>\n\n<p>我们再次除以 4。 欧米茄减去 200 年——我们来到了 19 世纪中叶,当时铁和火以越来越复杂的形式结合在一起,通过改进的蒸汽机为工业革命提供动力,基于博蒙特、帕潘、纽科门的工作 、瓦特和其他人(1600 年代至 1700 年代,超越了 1 世纪亚历山大港的 Heron [RAU1] 的第一台简单蒸汽机)。 电话(例如 Meucci 1857、Reis 1860、Bell 1876)[NASC3] 开始彻底改变通信方式。 疾病的细菌理论(巴斯德和科赫,1800 年代后期)彻底改变了医疗保健并使人们的平均寿命更长。 大约在 1850 年,以化肥为基础的农业革命(Sprengel & von Liebig,1800 年代初期)帮助引发了第二次人口爆炸,并在 20 世纪达到顶峰,当时世界人口翻了两番,让 20 世纪在所有世纪中脱颖而出 人类的历史,由制造人造肥料的 Haber-Bosch 工艺驱动,如果没有人造肥料,世界最多只能养活 40 亿人。[HAB1-2]</p>\n\n<p>我们再除以 4。 欧米茄减去 50 年——差不多是 1990 年,20 世纪 3 场大战的结束:第一次世界大战、第二次世界大战和冷战。 最有价值的 7 家上市公司都是日本公司(如今大多数都在美国); 然而,中国和美国西海岸都开始迅速崛起,为 21 世纪奠定了基础。 通过手机和无线革命(基于 1800 年代发现的无线电波)以及面向所有人的廉价个人电脑,数字神经系统开始席卷全球。 WWW 是由 Tim Berners-Lee 在瑞士的欧洲粒子对撞机上创建的。 现代人工智能也大约在这个时候开始:第一辆真正的自动驾驶汽车于 1980 年代由 Ernst Dickmanns 团队在慕尼黑制造(到 1994 年,他们的机器人汽车以最高 180 公里/小时的速度在高速公路上行驶)。 [AUT] 那时候,我在写我 1987 年的毕业论文 [META1],它介绍了不仅用于学习而且用于元学习或学习学习的算法,[META] 通过经验学习更好的学习算法(现在很流行 主题 [DEC])。 然后是我们在 TU Munich 的奇迹年 1990-91[MIR],这是当今被引用最多的神经网络 [MOST] 和通过自我监督/无监督学习(见上文)进行现代深度学习的根源,[UN][UN0-3 ] LSTM/Highway Net/ResNet 原理(现在放在你智能手机的口袋里——见上文),[DL4][DEC][MOST] 人工好奇心和针对发明自己问题的代理的生成对抗性神经网络(见上文),[ AC90-AC20][PP-PP2][SA17] 具有线性化自注意力的变压器(见上文),[FWP0-6][TR5-6] 将教师 NN 提取为学生 NN(见上文),[UN][UN0- 3] 在多个抽象层次和多个时间尺度上学习行动计划(见上文),[HRL0-2][LEC] 和其他令人兴奋的东西。 其中大部分已经变得非常流行,并改善了数十亿人的生活。[DL4][DEC][MOST]</p>\n\n<p>我们再次除以 4。Omega 减去 13 年——这是不久的将来的一个时间点,大约在 2030 年,届时许多人预测廉价的 AI 将具有人类的脑力。 然后是最后 13 年左右,直到 Omega,那时不可思议的事情将会发生(尽管对这一切持保留态度 [OMG1])。</p>\n\n<p>但当然,时间不会因欧米茄而停止。 也许只有人类主导的历史才会结束。 在 Omega 之后,许多好奇的元学习 AI 发明了自己的目标(这些目标已经在我的实验室中存在了几十年[AC][AC90,AC90b])将迅速改进自己,仅受限于可计算性和物理学的基本限制。</p>\n\n<p>超级智能人工智能会做什么? 太空对人类充满敌意,但对设计合理的机器人友好,它提供的资源比我们的生物圈薄膜要多得多,后者吸收的太阳能量不到太阳能的十亿分之一。 虽然一些好奇的 AI 会继续对生命着迷,至少只要他们还没有完全理解生命,[ACM16][FA15][SP16][SA17] 大多数人会对机器人和软件生命的令人难以置信的新机会更感兴趣 在太空中。 通过小行星带及更远地区无数的自我复制机器人工厂,它们将改造太阳系,然后在几十万年内改造整个银河系,并在数百亿年内改造可及宇宙的其余部分。 尽管存在光速限制,但不断扩大的 AI 领域将有足够的时间来殖民和塑造整个可见宇宙。</p>\n\n<p>让我稍微扩展一下你的想法。 宇宙还很年轻,只有 138 亿岁。 还记得我们一直除以 4 吗? 现在让我们乘以 4! 让我们展望未来,宇宙的年龄将是现在的 4 倍:大约 550 亿年。 届时,可见的宇宙将充满智慧。 因为在 Omega 之后,大多数 AI 将不得不去大多数物理资源所在的地方,以制造更多更大的 AI。 那些没有的不会有影响。[ACM16][FA15][SP16]</p>\n\n<p><img src=\"/img/src/2023-01-17-juergen-deep-learning-history-31.jpg\" alt=\"image\" /></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】当下生成式 AI(AIGC)领域的应用图景</title>\n \t<meta name=\"description\" content=\"随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】当下生成式 AI(AIGC)领域的应用图景</h2>\t\t\n\t<time datetime=\"2023-01-13T18:09:43+00:00\" class=\"by-line\">13 Jan 2023, 杭州 | Ollie Forsyth | [译] AI & 麦克船长 | 总计 8861 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-15-antler-generative-ai-1.jpg\" alt=\"image\" /></p>\n\n<p>本文译自 Antler Blog,原作者 Ollie Forsyth,中文译文由 AI 及麦克船长完成翻译。</p>\n\n<p>随着 ChatGPT 和 DALL-E 的发布,2022 年社交媒体平台上最热门的话题之一在最近几周爆发,引发了关于其对全球人员、职业和行业影响的激烈辩论。 争议的核心是什么? 生成式 AI (Gen-AI)——可以快速创建新内容的系统,例如大学论文、歌曲和数字艺术作品。 这些能力令人印象深刻,但它们也引发了关于工作的未来以及人类在 AI 主导的世界中的作用的重要问题。 随着生成式人工智能的不断发展,考虑伦理意义和对社会的潜在影响将变得至关重要。 如果创造性工作在很大程度上被人工智能机器取代,会发生什么?</p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是-gen-ai\" id=\"markdown-toc-什么是-gen-ai\">什么是 Gen-AI?</a></li>\n <li><a href=\"#人工智能与生成人工智能\" id=\"markdown-toc-人工智能与生成人工智能\">人工智能与生成人工智能</a></li>\n <li><a href=\"#广阔的机遇正在展开\" id=\"markdown-toc-广阔的机遇正在展开\">广阔的机遇正在展开</a></li>\n <li><a href=\"#gen-ai的影响\" id=\"markdown-toc-gen-ai的影响\">Gen-AI的影响</a></li>\n <li><a href=\"#培训模型在实践中如何运作\" id=\"markdown-toc-培训模型在实践中如何运作\">培训模型在实践中如何运作?</a></li>\n <li><a href=\"#语言模型是如何创建的\" id=\"markdown-toc-语言模型是如何创建的\">语言模型是如何创建的?</a></li>\n <li><a href=\"#为什么-gen-ai-存在\" id=\"markdown-toc-为什么-gen-ai-存在\">为什么 Gen-AI 存在?</a></li>\n <li><a href=\"#展望未来gen-ai收入模式\" id=\"markdown-toc-展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</a></li>\n <li><a href=\"#为什么现在\" id=\"markdown-toc-为什么现在\">为什么现在?</a></li>\n <li><a href=\"#gen-ai筹款格局\" id=\"markdown-toc-gen-ai筹款格局\">Gen-AI筹款格局</a></li>\n <li><a href=\"#gen-ai独角兽格局\" id=\"markdown-toc-gen-ai独角兽格局\">Gen-AI独角兽格局</a></li>\n <li><a href=\"#趋势\" id=\"markdown-toc-趋势\">趋势:</a> <ul>\n <li><a href=\"#gen-ai-如何用于艺术和音乐\" id=\"markdown-toc-gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</a></li>\n <li><a href=\"#gen-ai-如何用于游戏\" id=\"markdown-toc-gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</a></li>\n <li><a href=\"#生成式-ai-将会如何影响创作者经济\" id=\"markdown-toc-生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</a></li>\n </ul>\n </li>\n <li><a href=\"#这个空间的未来是什么它可能面临什么挑战\" id=\"markdown-toc-这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</a></li>\n <li><a href=\"#gen-ai-将影响元宇宙具体如何影响还有待观察\" id=\"markdown-toc-gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</a></li>\n <li><a href=\"#让我们一起塑造未来\" id=\"markdown-toc-让我们一起塑造未来\">让我们一起塑造未来</a></li>\n <li><a href=\"#参考链接\" id=\"markdown-toc-参考链接\">参考链接</a></li>\n</ul>\n\n<p>这份报告深入探讨了 Gen-AI 的世界,并且是第一份面向所有人的综合市场地图。 我们概述了该领域的 160 多个平台及其投资者,以及领先思想领袖对这项技术潜力的见解。 这为读者提供了一个独特的机会,可以全面了解生成人工智能市场以及新玩家挑战谷歌等老牌玩家的潜力。</p>\n\n<blockquote>\n <p>“生成式 AI 是一项基础技术,并且与这些新平台一样,它带来的机会很多——我们已经过了‘如果’的阶段,我们正处于‘何时’和‘如何’的阶段。” 随着 LLM 开源,我们看到基础设施层日趋成熟和民主化,这加速了应用层。”——Irina Elena Haivas,Atomico 的投资者和合伙人</p>\n</blockquote>\n\n<p>请注意:本文提供的信息基于 Antler 的零投资日方法和我们为全球创始人提供的支持。 我们行业地图中的特色平台来自 Crunchbase。 值得注意的是,其中一些平台可能与 AI 和 Gen-AI 相交。 如果您认为您的平台应该包含在我们未来的映射中,请通过 Ollie.Forsyth@antler.co 与我们联系。</p>\n\n<h2 id=\"什么是-gen-ai\">什么是 Gen-AI?</h2>\n\n<p>想象这样一个世界,您可以使用生成式辅助工具在几分钟内完成您的项目,而不是花几天时间写一篇博客文章、一周时间创建演示文稿或几个月时间写一篇学术论文。 这些工具不仅帮助我们完成项目,还支持我们做出更好的决策。</p>\n\n<p>以下是 Gen-AI 平台可能变得多么强大的一个例子:对于那些熟悉我们关于创作者经济的报告的人来说,想象一个世界,在这个世界里,创作者可以将他们的内容上传到任何语言,并用他们自己的声音作为画外音,而不是依赖 在机器人或本地翻译器上。 这是一个美丽的新世界,在这里我们可以获得强大的工具,可以节省我们无数的时间并提高我们的工作效率。</p>\n\n<blockquote>\n <p>“我们正处于生成人工智能的转折点,原因有二:计算机可以比以往任何时候都更好地创造,而且人们与它们的互动从未如此简单。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-2.jpg\" alt=\"image\" /></p>\n\n<blockquote>\n <p>“在 Media Monks,我们相信生成式 AI 将对我们的行业产生重大影响,尽管很难想象这项惊人技术的真正范围。 我们研究生成式人工智能已有大约五年时间,创新速度呈指数级增长。 技术的进步发生在我们的生产时间表内,范围从 1 到 6 个月不等。 这意味着我们在项目开始时使用的工具在我们上线时已经过时了。” — Media Monks 的创意 AI 设计师兼工程师 Samuel Snider Held。</p>\n</blockquote>\n\n<h2 id=\"人工智能与生成人工智能\">人工智能与生成人工智能</h2>\n\n<p>人工智能 (AI) 是一个广义术语,指的是任何能够实现智能行为的技术。 这可能包括范围广泛的技术,从可以对数据进行排序的简单算法,到可以模仿类人思维过程的更先进的系统。</p>\n\n<p>另一方面,生成式人工智能 (Gen-AI) 是一种特定类型的人工智能,专注于生成新内容,例如文本、图像或音乐。 这些系统在大型数据集上进行训练,并使用机器学习算法生成与训练数据相似的新内容。 这在各种应用程序中都很有用,例如创作艺术、音乐,甚至为聊天机器人生成文本。</p>\n\n<p>从本质上讲,人工智能是一个广义的术语,涵盖了许多不同的技术,而生成人工智能是一种专注于创造新内容的特定类型的人工智能。</p>\n\n<h2 id=\"广阔的机遇正在展开\">广阔的机遇正在展开</h2>\n\n<p>未来,Gen-AI 很可能会对创意产业产生重大影响。 虽然一些创意可能会被 Gen-AI 系统取代,但其他创意可能会找到新的机会来使用这些系统或创建由 Gen-AI 支持的内容。 在许多情况下,它实际上可以增强创意人员的工作,使他们能够创建更加个性化或独特的内容,或者产生新的想法和概念,如果不使用 AI,这些想法和概念可能是不可能的。</p>\n\n<p>Gen-AI 对创意人员的一个潜在好处是,它可以使他们能够更快、更高效地创建内容。 例如,作家可以使用 Gen-AI 系统生成文章或故事的草稿,然后他们可以对其进行编辑和完善。 这可以节省时间并让创意人员专注于工作中最重要的方面。</p>\n\n<p>“生成式 AI 是一股巨大的浪潮,它将在几乎所有行业中产生不可避免的涟漪,对于其中的绝大多数,我们认为这将带来难以置信的增值。我们看到了最大的机会,因为平台是建立在基础之上的 模型,其中用户体验、可访问性和嵌入性将成为这场比赛的关键差异化因素。所有这些都需要由杀手级的上市战略提供动力,最重要的是,速度!下半年将是关键。” ——Stephanie Chan,Samaipata Ventures 投资人。</p>\n\n<h2 id=\"gen-ai的影响\">Gen-AI的影响</h2>\n\n<p>根据使用方式的不同,这项技术可能会产生许多不同的影响。 例如,Gen-AI 可用于创建新的内容,如音乐或图像,这些内容可用于多种用途,例如为创意者提供更多的灵活性和想象力。 它还可用于通过生成新的训练数据来改进机器学习算法。 总的来说,Gen-AI 的影响肯定是巨大的,因为它有潜力创造新的有用内容并提高机器学习系统的性能。</p>\n\n<blockquote>\n <p>“我们正在走向人工智能广泛应用的时代。 但广泛可用和实际可用于实现业务成果是两件截然不同的事情。” —Dave Rogenmoser,Jasper 的首席执行官兼联合创始人。</p>\n</blockquote>\n\n<h2 id=\"培训模型在实践中如何运作\">培训模型在实践中如何运作?</h2>\n\n<p>Gen-AI 训练模型通过从大量示例数据集中学习并使用该知识生成与训练数据集中示例相似的新数据来工作。 这通常是使用一种称为生成模型的机器学习算法来完成的。有许多不同类型的生成模型,每种模型都使用不同的方法来生成新数据。 一些常见类型的生成模型包括生成对抗网络 (GAN)、变分自动编码器 (VAE) 和自回归模型。</p>\n\n<p>例如,在人脸图像数据集上训练的生成模型可能会学习人脸的一般结构和外观,然后使用这些知识生成新的、以前未见过的看起来真实可信的人脸。</p>\n\n<p>生成模型用于各种应用程序,包括图像生成、自然语言处理和音乐生成。 它们对于手动生成新数据困难或昂贵的任务特别有用,例如在为产品创建新设计或生成逼真的语音的情况下。</p>\n\n<blockquote>\n <p>“这些新的基础模型以及建立在其上的应用程序加快了许多行业的步伐:为游戏和社交媒体公司生成创意内容,自动化企业内部的手动流程,帮助扩大以前无法想象的业务,如电影、音乐和漫画制作—— 可能性是无限的。”——Manjot Pahwa,Lightspeed Venture Partners 的投资者</p>\n</blockquote>\n\n<h2 id=\"语言模型是如何创建的\">语言模型是如何创建的?</h2>\n\n<p>创建语言模型的方法有多种,但最常见的方法是使用机器学习算法在现有文本的大型数据集上训练模型。 此过程通常包括以下步骤:</p>\n\n<ol>\n <li>收集现有文本的大型数据集。 此数据集应代表您希望模型能够生成的语言或文本样式。</li>\n <li>预处理文本数据以清理并准备训练。 这通常涉及将文本标记为单个单词或短语,并将所有单词转换为小写。</li>\n <li>在预处理的文本数据上训练机器学习算法。 这可以使用多种算法来完成,包括递归神经网络 (RNN) 和长短期记忆 (LSTM) 网络。</li>\n <li>通过调整模型的参数和超参数以及在必要时使用额外的训练数据来微调训练模型。</li>\n <li>通过使用经过训练的模型生成示例文本并评估结果来测试模型。 这可以通过将生成的文本与原始训练数据进行比较,或使用其他指标(例如困惑度或 BLEU 分数)来完成。</li>\n <li>通过重复步骤 4 和 5 来优化模型,直到生成的文本具有高质量并匹配所需的语言或样式。</li>\n</ol>\n\n<p>“重要的是要注意,创建语言模型需要大量的计算资源和机器学习方面的专业知识——尽管这个空间还很早,但平台正在花费数百万美元来微调他们的产品和服务。</p>\n\n<blockquote>\n <p>生成式 AI 类别的创始人当前面临的挑战不仅是要构建产品,还要构建具有持久能力的可防御商业模型。 任何有能力的开发人员都可以围绕这些底层生成引擎包装应用程序皮肤。 解决方案是通过嵌入网络效应、提高转换成本、根深蒂固的产品合作伙伴关系等策略,整合可持续的竞争差异化。”——David Beisel,NextView Ventures 合伙人。</p>\n</blockquote>\n\n<h2 id=\"为什么-gen-ai-存在\">为什么 Gen-AI 存在?</h2>\n\n<p>Gen-AI 的存在是因为它有可能解决许多重要问题,并为广泛领域的无数新机遇打开大门。 Gen-AI 成为一个不断发展的研发领域的一些关键原因包括:</p>\n\n<ul>\n <li>Gen-AI 可以创造新的内容。 Gen-AI 的主要优势之一是它能够生成新内容,例如文本、图像或音乐。 这可用于创造新的艺术、音乐和其他形式的创造性表达,并生成用于训练机器学习模型的数据。</li>\n <li>Gen-AI 可以提高效率和生产力。 通过自动生成内容,Gen-AI 可以帮助节省时间并减少人工劳动。 这可以提高各个领域的效率和生产力,从新闻和内容创建到数据注释和分析。</li>\n <li>Gen-AI 可以提高生成内容的质量。 随着机器学习和自然语言处理的进步,Gen-AI 变得越来越复杂,能够生成人类难以与真实内容区分开来的高质量内容。</li>\n <li>Gen-AI 可以启用新的应用程序和用途。 Gen-AI 创造新内容的能力为新的应用和用途开辟了许多可能性。 例如,它可用于创建个性化体验,例如个性化新闻文章或个性化音乐推荐。</li>\n</ul>\n\n<blockquote>\n <p>“这并不广为人知。 我的观点是,生成式 AI 模型现在很神奇,因为它们已经能够通过语言接收人们的输入。因为它们能够代表如此多的不同概念——并将它们结合起来——它们可以产生美丽、狂野和创造性的结果。 这令人兴奋、激动,也许还有点可怕。 对于创意人员来说,这意味着通过灵感来寻找灵感,更快地创建原型,并结合模型 (Photoshop++) 的技能来完善作品。’’——Sharon Zhou。</p>\n</blockquote>\n\n<h2 id=\"展望未来gen-ai收入模式\">展望未来——Gen-AI收入模式</h2>\n\n<p>使用 Gen-AI 技术的公司有几种潜在的收入模式。 一些可能的收入来源包括:</p>\n\n<ul>\n <li>将技术许可给可以使用它来改进其产品或服务的其他公司或组织。</li>\n <li>将 AI 系统的输出(例如生成的图像、视频或文本)出售给可以将它们用于各种目的的客户。</li>\n <li>提供对人工智能系统的访问作为订阅服务,客户可以使用它来生成自己的输出</li>\n <li>使用 AI 系统提高公司现有产品或服务的效率或有效性,然后向客户收取这些增强产品的费用。</li>\n <li>创建利用 AI 系统功能的新产品或服务,并将其直接销售给客户。</li>\n</ul>\n\n<h2 id=\"为什么现在\">为什么现在?</h2>\n\n<p>现在是 Gen-AI 时代的几个原因。 首先,机器学习和自然语言处理的进步使人工智能系统能够生成高质量的、类似人类的内容。 其次,艺术、营销和娱乐等领域对个性化和独特内容的需求不断增长,增加了对 Gen-AI 平台的需求。 第三,大量数据和强大计算资源的可用性使得大规模训练和部署这些类型的模型成为可能。</p>\n\n<blockquote>\n <p>“人们曾承诺人工智能将改变世界,自 2012 年以来我们一直在等待。在过去的两三年里,终于发生了一些变化。 虽然最近围绕生成 AI 的兴奋一直是文本到图像,但我相信 AI 驱动的文本生成将被证明更具变革性。 现在,随着越来越多地使用尖端语言模型,我们看到这项技术扩散到日常产品中——彻底改变了公司开展业务的方式,并重新构想了人类体验技术的方式。”——Aidan Gomez,Cohere 联合创始人兼首席执行官。</p>\n</blockquote>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-3.jpg\" alt=\"image\" /></p>\n\n<p>阅读我们的 Gen-AI 初创公司完整列表(定期更新)</p>\n\n<p>Gen-AI 类别说明:</p>\n\n<ul>\n <li>文本:总结或自动化内容。</li>\n <li>图像:生成图像。</li>\n <li>音频:总结、生成或转换音频中的文本。</li>\n <li>视频:生成或编辑视频。</li>\n <li>代码:生成代码。</li>\n <li>聊天机器人:自动化客户服务等。</li>\n <li>机器学习平台:应用程序/机器学习平台。</li>\n <li>搜索:人工智能驱动的洞察力。</li>\n <li>游戏:Gen-AI 游戏工作室或应用程序。</li>\n <li>数据:设计、收集或总结数据。</li>\n</ul>\n\n<h2 id=\"gen-ai筹款格局\">Gen-AI筹款格局</h2>\n\n<p>由于许多投资者专注于 Gen-AI 领域,我们列出了最活跃的投资者:</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-4.jpg\" alt=\"image\" /></p>\n\n<p>少数投资于 Gen-AI 领域的投资者。 这些投资者也可能投资于后期或早期阶段的公司。</p>\n\n<h2 id=\"gen-ai独角兽格局\">Gen-AI独角兽格局</h2>\n\n<p>尽管该行业仍在兴起,但一些独角兽已经出现。 到目前为止,2019 年生产了两只独角兽,2020 年生产了一只,2022 年生产了四只。</p>\n\n<p><img src=\"/img/src/2023-01-15-antler-generative-ai-5.jpg\" alt=\"image\" /></p>\n\n<h2 id=\"趋势\">趋势:</h2>\n\n<h3 id=\"gen-ai-如何用于艺术和音乐\">Gen-AI 如何用于艺术和音乐?</h3>\n\n<p>Gen-AI 正以几种不同的方式用于艺术和音乐。 一个常见的应用是使用生成模型来创造新的艺术和音乐,方法是从头开始生成全新的作品,或者以现有作品为起点并向其中添加新元素。 例如,生成模型可能会在大型绘画数据集上进行训练,然后用于生成与数据集中的作品相似但又独特且原创的新绘画。</p>\n\n<h3 id=\"gen-ai-如何用于游戏\">Gen-AI 如何用于游戏?</h3>\n\n<p>Gen-AI 正以多种方式用于游戏,包括创建新的关卡或地图、生成新的对话或故事情节,以及创建新的虚拟环境。 例如,游戏可能会使用 Gen-AI 模型来创建一个新的、独特的关卡,供玩家在每次玩游戏时探索,或者根据玩家的动作为非玩家角色生成新的对话选项。 此外,Gen-AI 可用于创建新的、逼真的虚拟环境供玩家探索,例如城市、森林或行星。 总的来说,它可以用来为游戏体验增加一定程度的活力和多样性,使它们对玩家来说更具吸引力和身临其境。</p>\n\n<blockquote>\n <p>‘“一般而言,短期的创新领域会非常积极。 众所周知,游戏和在线 3D 体验难以构建——生成式 AI 将彻底颠覆这一现状,让游戏资产的创建变得更加容易。 在游戏中应用生成式 AI 的潜在缺点,或者更确切地说是后果,更为现实。 虽然像 AI 生成的文案或图像创建这样的单维应用程序只是我们执行的现有任务的放大器,但仍然允许我们控制输出的应用程序(即,我们可以决定接受/拒绝一份副本并决定在哪里 使用副本),我们在游戏中与 AI 的交互将更加多维。 随着时间的推移,AI(无论是环境、行为还是 NPC 角色)将进化并适应人类的注意,同样,人类将习惯于在这些 AI 生成的领域中进行社交和定期互动。”——Roblox 的 Annie Zhang。</p>\n</blockquote>\n\n<h3 id=\"生成式-ai-将会如何影响创作者经济\">生成式 AI 将会如何影响创作者经济?</h3>\n\n<p>创作者经济已经是一个价值 1000 亿美元的行业,正准备持续颠覆,Gen-AI 可能会对创意产生重大影响,尤其是那些创作音乐、艺术或写作的人。 然而,它确实为创作者提供了从第一天起就走向全球的机会,允许他们的内容使用创作者的声音转化为任何语言,或者将他们的创造力转化为更具吸引力的内容。</p>\n\n<blockquote>\n <p>“生成式 AI 会将创作者变成超级英雄,并扩大他们不那么强大的领域。更多地将其视为创作者的副驾驶,而不是创作者的替代者。” ——Jim Louderback,Inside The Creator Economy 的作者。</p>\n</blockquote>\n\n<p>为了让创作者经济取得成功,平台需要适应创作者的个性,以便在内容可能主要由 AI 平台支持时,创作者与他们的粉丝建立某种形式的联系。</p>\n\n<blockquote>\n <p>“我认为人的因素对于艺术具有价值是必不可少的。 当 AI 生成的艺术是由算法和机器创造的,而不是由具有自己的经验、情感和观点的个人创造时,它可以被视为缺乏通常被视为伟大艺术必不可少的真实性和人性。 这可能会使一些观众难以在情感层面上与 AI 生成的艺术产生联系,从而降低其影响力和重要性。”——创作者 Ivona Tau。</p>\n</blockquote>\n\n<p>然而,当我们问创作者 Gen-AI 将对他们产生什么影响时,一位创作者说:</p>\n\n<blockquote>\n <p>“不多。 也就是说,我正怀着极大的兴趣关注正在发生的事情。 其他人在生成模型的帮助下获得的结果让我深受启发。 你经常听到艺术家将 AI 图像模型称为“工具”,但 AI 不仅仅是一种工具。 它是创意伙伴、合成精灵或鼓舞人心的盟友。”——艺术家詹姆斯·格尼 (James Gurney)。</p>\n</blockquote>\n\n<h2 id=\"这个空间的未来是什么它可能面临什么挑战\">这个空间的未来是什么,它可能面临什么挑战?</h2>\n\n<p>Gen-AI 面临许多挑战,包括提高这些模型产生的输出的质量和多样性,提高它们生成输出的速度,并使它们更加健壮和可靠。 另一个主要挑战是开发生成式 Gen-AI 模型,这些模型能够更好地理解和整合他们正在处理的数据的底层结构和上下文,以便产生更准确和连贯的输出。 此外,对于生成式人工智能的伦理和社会影响,以及如何确保以负责任和有益的方式使用这些技术,也存在持续的担忧。</p>\n\n<p>让我们仔细看看其中的一些问题:</p>\n\n<p><strong>版权</strong>。 截至今天,要了解这些平台如何识别真实的原始来源或艺术作品的来源是一项挑战——这些模型是由数亿个数据点训练的。 创作者担心这些平台将如何减轻对创作者作品的版权侵权。 正如我们在 Lauryn Ipsum 发布的最近一个案例中看到的那样,Lensa 应用程序中使用的图像具有原始艺术家签名的背景。</p>\n\n<blockquote>\n <p>“目前生成人工智能中最紧迫的问题之一是系统可信度。 像 OpenAI 的 ChatGPT 这样的大型语言模型很容易分享不正确或错误的响应。 在图像生成中,系统已经接受了大量图像的训练,系统输出存在版权和知识产权问题,使企业用户不确定将它们集成到产品或工作流程中。”——Molly Welch,Radical Ventures 的投资者。</p>\n</blockquote>\n\n<p><strong>学生写论文</strong>。 随着这些平台变得更加智能,精明的年轻学生将在日常生活中采用它们。 这将如何影响他们的学术工作,他们的教授将如何确定这是否真的是他们的工作? Gen-AI 将对教育领域产生巨大影响,这还有待观察。</p>\n\n<blockquote>\n <p>“假设 ChatGPT 模型不断改进,学生使用 chatGPT 来补充学习的机会是无穷无尽的。 学生可以使用它来生成测验和抽认卡的内容,以帮助他们学习、优化现有代码,甚至为学习指南编写摘要。 这里的关键词是补充。 除了他们自己已经投入的原创作品之外,学生还应该使用 ChatGPT。当学生使用 ChatGPT 内容代替他们的作品,甚至提交 ChatGPT 内容作为他们自己的原创想法时,ChatGPT 可能会出现问题。 大学行政部门和学生需要共同努力制定政策,明确说明这个新世界可以接受的内容。 上周我参加了一次开卷考试,明确禁止使用 ChatGPT 或任何其他人工智能支持。” —Cherie Lou,斯坦福大学的创作者和学生。</p>\n</blockquote>\n\n<p><strong>虚假信息与错误信息</strong>。尽管这些系统非常聪明,但有时它们不可避免地会提供错误信息。 例如,最近在英国第 4 频道的一次采访中,主持人向 Open AI 询问他的职业道路,聊天机器人助手给出了不准确的信息。 随着训练模型变得更具适应性并更多地了解我们,最终算法中的错误将会减少。</p>\n\n<p>Gen-AI 的缺点包括:</p>\n\n<ul>\n <li>如果训练数据不够多样化或不够具有代表性,则生成的数据存在偏差风险。</li>\n <li>对生成人工智能在某些行业取代人类劳动的潜力的担忧,导致失业。</li>\n <li>Gen-AI 被用于恶意目的的可能性,例如制造假新闻或冒充个人。</li>\n</ul>\n\n<p>Gen-AI 有可能取代从设计师到制作人再到艺术家的数百万个工作岗位; 但是,创意总是会在某些方面存在。</p>\n\n<h2 id=\"gen-ai-将影响元宇宙具体如何影响还有待观察\">Gen-AI 将影响元宇宙——具体如何影响还有待观察。</h2>\n\n<p>很难准确预测生成式 AI 将如何影响元宇宙,因为后者在很大程度上仍是一个理论概念,并且对于它的外观或功能尚无共识。 然而,Gen-AI 将在其创造和发展中发挥重要作用,因为它将允许在虚拟世界中自动生成内容和体验。 这可能会导致更加身临其境和动态的元宇宙,几乎可以无限地提供新的和独特的体验供用户享受。 Gen-AI 也有可能用于在元宇宙中自动执行各种任务,例如管理虚拟经济并确保虚拟世界保持稳定和正常运行。 总体而言,Gen-AI 对元宇宙的影响可能是重大而广泛的。</p>\n\n<blockquote>\n <p>“人工智能堆栈的不同层级将存在商机,我们已经看到一些商业模式正在出现。 显然,生产像 GPT-3 这样的基础模型非常昂贵和复杂,少数能够做到这一点的公司将获得丰厚的报酬。 但是,有无数机会开发更专业的模型并将通用功能捆绑到特定目标市场需要的东西中。 这相当于垂直SaaS,应用于AI。 我们可能会看到许多支持 AI 的 SaaS 游戏,它们为特定市场提供具有出色 UX 的整体解决方案。在堆栈的更下方,提供正确类型的训练数据,使 ML 工程师能够快速构建专业模型并 确保模型的稳健性都是非常可行的业务。”—Andreas Goeldi,BTOV Ventures 的合伙人。</p>\n</blockquote>\n\n<h2 id=\"让我们一起塑造未来\">让我们一起塑造未来</h2>\n\n<p>准备好迎接将彻底改变未来工作方式的技术转变! 我们正处在一个新时代的边缘,成千上万的工作岗位将被改变,新的工作岗位将被创造出来。 这些尖端的 Gen-AI 平台无疑将支持和改善我们的日常生活,但我们需要时间才能完全适应它们。</p>\n\n<blockquote>\n <p>“这种前所未有的人机协作水平正在如火如荼地进行,无论你身处哪个行业,无论你身处哪个行业,无论谁率先全面整合生成式 AI 方法,游戏现在都向他们开放。”——Gabrielle Chou,副教授 上海纽约大学。</p>\n</blockquote>\n\n<h2 id=\"参考链接\">参考链接</h2>\n\n<ul>\n <li>https://www.antler.co/blog/generative-ai</li>\n</ul>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</title>\n \t<meta name=\"description\" content=\"2022 年是生成式 AI(Gen-AI)的元年,而游戏领域也正在被生成式 AI 进行着生产力革命。当下游戏 2D 素材、3D 建模、音频内容、实时生成智能语音交互 …… 等等一系列技术在游戏世界里率先应用,正在推动一个让玩家更可以全方位实时交互的游戏世界的诞生,而不再像以前一样只能依赖以往设定好的游戏交互内容,这令人感到无比兴奋。而这些技术在虚拟世界成熟后,将会逐渐渗透回现实世界中的各项应用,尤其是创作者生态的生产力变革,更进一步地影响普通人日常的内容获取与 AI 交互。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【编译】游戏生产力革命:生成式 AI(AIGC)正在深度变革游戏领域</h2>\t\t\n\t<time datetime=\"2023-01-11T18:33:49+00:00\" class=\"by-line\">11 Jan 2023, 杭州 | James Gwertzman and Jack Soslow | [译] AI & 麦克船长 | 总计 10142 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-0.png\" alt=\"image\" /></p>\n\n<ul>\n <li>作者 James Gwertzman and Jack Soslow</li>\n <li>[译] AI & 麦克船长</li>\n <li>本文授权首发媒体「锐察力」,微信公众号 ID @ruichali</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#什么是生成式人工智能\" id=\"markdown-toc-什么是生成式人工智能\">什么是生成式人工智能</a></li>\n <li><a href=\"#part-1观察和预测\" id=\"markdown-toc-part-1观察和预测\">Part 1、观察和预测</a> <ul>\n <li><a href=\"#一假设\" id=\"markdown-toc-一假设\">一、假设</a> <ul>\n <li><a href=\"#1通用人工智能的研究量将继续增长创造出更有效的技术\" id=\"markdown-toc-1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</a></li>\n <li><a href=\"#2在所有娱乐中游戏将受生成人工智能的影响最大\" id=\"markdown-toc-2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</a></li>\n <li><a href=\"#3游戏制作中涉及的每一项资产都会有一个生成式ai模型\" id=\"markdown-toc-3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</a></li>\n <li><a href=\"#4内容价格将大幅下降在某些情况下实际上会降为零\" id=\"markdown-toc-4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</a></li>\n <li><a href=\"#5我们还处于这场革命的初级阶段很多实践还需要完善\" id=\"markdown-toc-5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</a></li>\n </ul>\n </li>\n <li><a href=\"#二预测\" id=\"markdown-toc-二预测\">二、预测</a> <ul>\n <li><a href=\"#1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\" id=\"markdown-toc-1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</a></li>\n <li><a href=\"#2降低壁垒将带来更多的冒险精神和创造性探索\" id=\"markdown-toc-2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</a></li>\n <li><a href=\"#3人工智能辅助的微游戏工作室兴起\" id=\"markdown-toc-3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</a></li>\n <li><a href=\"#4每年发行的游戏数量增加\" id=\"markdown-toc-4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</a></li>\n <li><a href=\"#5生成式-ai-之前不可能创建的新游戏类型\" id=\"markdown-toc-5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</a></li>\n <li><a href=\"#6价值将归于行业特定的人工智能工具而不仅仅是基础模型\" id=\"markdown-toc-6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</a></li>\n <li><a href=\"#7法律挑战来了\" id=\"markdown-toc-7法律挑战来了\">7、法律挑战来了</a></li>\n <li><a href=\"#8节目不会像艺术内容那样受到严重破坏至少现在还没有\" id=\"markdown-toc-8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</a></li>\n </ul>\n </li>\n <li><a href=\"#三建议\" id=\"markdown-toc-三建议\">三、建议</a> <ul>\n <li><a href=\"#1现在开始探索生成式-ai\" id=\"markdown-toc-1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</a></li>\n <li><a href=\"#2寻找市场地图机会\" id=\"markdown-toc-2寻找市场地图机会\">2、寻找市场地图机会</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#part-2市场地图\" id=\"markdown-toc-part-2市场地图\">Part 2、市场地图</a> <ul>\n <li><a href=\"#一市场现状\" id=\"markdown-toc-一市场现状\">一、市场现状</a></li>\n <li><a href=\"#二2d-图像\" id=\"markdown-toc-二2d-图像\">二、2D 图像</a> <ul>\n <li><a href=\"#1概念艺术\" id=\"markdown-toc-1概念艺术\">1、概念艺术</a></li>\n <li><a href=\"#2二维制作艺术\" id=\"markdown-toc-2二维制作艺术\">2、二维制作艺术</a></li>\n </ul>\n </li>\n <li><a href=\"#三3d-图稿\" id=\"markdown-toc-三3d-图稿\">三、3D 图稿</a> <ul>\n <li><a href=\"#13d资产\" id=\"markdown-toc-13d资产\">1、3D资产</a></li>\n <li><a href=\"#23d-纹理\" id=\"markdown-toc-23d-纹理\">2、3D 纹理</a></li>\n <li><a href=\"#3动画\" id=\"markdown-toc-3动画\">3、动画</a></li>\n <li><a href=\"#4关卡设计和世界建设\" id=\"markdown-toc-4关卡设计和世界建设\">4、关卡设计和世界建设</a></li>\n </ul>\n </li>\n <li><a href=\"#四声音\" id=\"markdown-toc-四声音\">四、声音</a> <ul>\n <li><a href=\"#1声音特效\" id=\"markdown-toc-1声音特效\">1、声音特效</a></li>\n <li><a href=\"#2音乐\" id=\"markdown-toc-2音乐\">2、音乐</a></li>\n <li><a href=\"#3语音和对话\" id=\"markdown-toc-3语音和对话\">3、语音和对话</a></li>\n </ul>\n </li>\n <li><a href=\"#五npc-或玩家角色\" id=\"markdown-toc-五npc-或玩家角色\">五、NPC 或玩家角色</a></li>\n <li><a href=\"#六多合一平台\" id=\"markdown-toc-六多合一平台\">六、多合一平台</a></li>\n <li><a href=\"#七结论\" id=\"markdown-toc-七结论\">七、结论</a></li>\n </ul>\n </li>\n</ul>\n\n<p>要了解生成式 AI 将如何彻底改变游戏,只需看看 <a href=\"https://twitter.com/emmanuel_2m\">@emmanuel_2m</a> 最近发布的这篇 <a href=\"https://twitter.com/emmanuel_2m/status/1589995198289182720\">Twitter 帖子</a>。 在这篇文章中,他探讨了使用 Stable Diffusion + Dreambooth(流行的 2D 生成 AI 模型)为假设的游戏生成药水图像。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-1.jpg\" alt=\"image\" /></p>\n\n<p>这项工作的变革性不仅在于它节省了时间和金钱,同时还提供了质量——从而打破了经典的“成本、质量或速度只能有两个”的三角关系。艺术家们现在可以在几个小时内创作出高质量的图像,否则手工生成这些图像需要数周时间。 真正具有变革性的是:</p>\n\n<ul>\n <li>现在,任何可以学习一些简单工具的人都可以获得这种创造力。</li>\n <li>这些工具可以以高度迭代的方式创建无数的变体。</li>\n <li>一旦经过训练,这个过程就是实时的——结果几乎是即时可用的。</li>\n</ul>\n\n<p>自实时 3D 以来,还没有出现过对游戏具有如此革命性意义的技术。 花任何时间与游戏创作者交谈,兴奋和惊奇的感觉是显而易见的。 那么这项技术将走向何方? 它将如何改变游戏? 不过,首先,让我们回顾一下什么是生成式人工智能?</p>\n\n<h4 id=\"什么是生成式人工智能\">什么是生成式人工智能</h4>\n\n<p>生成式 AI 是机器学习的一种,计算机可以根据用户的提示生成原创的新内容。 今天,文本和图像是这项技术最成熟的应用,但几乎每个创意领域都在开展工作,从动画到音效,再到音乐,甚至创建具有完全充实个性的虚拟角色。</p>\n\n<p>当然,人工智能在游戏中并不是什么新鲜事。 即使是早期的游戏,如 Atari 的 Pong,也有计算机控制的对手来挑战玩家。 然而,这些虚拟敌人并没有像我们今天所知道的那样运行人工智能。 它们只是游戏设计师编写的脚本程序。 他们模拟了一个人工智能对手,但他们无法学习,他们只能和建造他们的程序员一样好。</p>\n\n<p>由于更快的微处理器和云,现在的不同之处在于可用的计算能力。 有了这种能力,就可以构建大型神经网络来识别高度复杂领域中的模式和表征。</p>\n\n<p>这篇博文分为两部分:</p>\n\n<ul>\n <li>第一部分包含我们对游戏生成 AI 领域的观察和预测。</li>\n <li>第二部分是我们的空间市场地图,概述了各个细分市场并确定了每个细分市场中的关键公司。</li>\n</ul>\n\n<h3 id=\"part-1观察和预测\">Part 1、观察和预测</h3>\n\n<h4 id=\"一假设\">一、假设</h4>\n\n<p>首先,让我们探讨一下这篇博文其余部分的一些假设:</p>\n\n<h5 id=\"1通用人工智能的研究量将继续增长创造出更有效的技术\">1、通用人工智能的研究量将继续增长,创造出更有效的技术</h5>\n\n<p>考虑一下 arXiv 档案中每月发表的关于机器学习或人工智能的学术论文数量图表:</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-2.jpg\" alt=\"image\" /></p>\n\n<p>如您所见,论文数量呈指数级增长,丝毫没有放缓的迹象。 这仅包括已发表的论文——许多研究甚至从未发表过,直接用于开源模型或产品研发。 结果是兴趣和创新的爆炸式增长。</p>\n\n<h5 id=\"2在所有娱乐中游戏将受生成人工智能的影响最大\">2、在所有娱乐中,游戏将受生成人工智能的影响最大</h5>\n\n<p>就涉及的资产类型(2D 艺术、3D 艺术、音效、音乐、对话等)的数量而言,游戏是最复杂的娱乐形式。 游戏也是最具互动性的,非常强调实时体验。 这为新游戏开发者创造了一个陡峭的进入壁垒,同时也为制作一款现代的、排行榜首的游戏付出了高昂的成本。 它还为生成式 AI 的颠覆创造了巨大的机会。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-3.jpg\" alt=\"image\" /></p>\n\n<p>想想像 Red Dead Redemption 2 这样的游戏,它是有史以来最昂贵的游戏之一,制作成本接近 5 亿美元。 原因很容易理解——它拥有市场上所有游戏中最美丽、最真实的虚拟世界之一。 它还花费了将近 8 年的时间打造,拥有超过 1,000 个不可玩的角色(每个角色都有自己的个性、艺术作品和配音演员),一个近 30 平方英里的世界,超过 100 个任务分为 6 个章节,以及 由 100 多位音乐家创作的近 60 小时的音乐。 这个游戏的一切都很大。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-4.jpg\" alt=\"image\" /></p>\n\n<p>现在将 Red Dead Redemption 2 与 Microsoft Flight Simulator 进行比较,后者不仅大,而且非常庞大。 Microsoft Flight Simulator 使玩家能够在整个地球上飞行,包括 1.97 亿平方英里的地球。 微软是如何打造如此庞大的游戏的? 通过让人工智能来做。 微软与 blackshark.ai 合作,训练人工智能从 2D 卫星图像生成逼真的 3D 世界。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-5.jpg\" alt=\"image\" /></p>\n\n<p>这是一个游戏的例子,如果不使用 AI,实际上是不可能构建的,而且,从这些模型可以随着时间的推移不断改进这一事实中获益。 例如,他们可以增强“高速公路三叶草立交桥”模型,重新运行整个构建过程,并突然之间,整个星球上的所有高速公路立交桥都得到了改善。</p>\n\n<h5 id=\"3游戏制作中涉及的每一项资产都会有一个生成式ai模型\">3、游戏制作中涉及的每一项资产都会有一个生成式AI模型</h5>\n\n<p>到目前为止,像 Stable Diffusion 或 MidJourney 这样的 2D 图像生成器已经获得了生成式 AI 的大部分流行兴奋,因为它们可以生成图像的引人注目的特性。 但是,已经存在适用于游戏中几乎所有资产的生成式 AI 模型,从 3D 模型到角色动画,再到对话和音乐。 这篇博文的后半部分包括一张市场地图,突出显示了一些专注于每种类型内容的公司。</p>\n\n<h5 id=\"4内容价格将大幅下降在某些情况下实际上会降为零\">4、内容价格将大幅下降,在某些情况下实际上会降为零。</h5>\n\n<p>在与正在尝试将生成式 AI 集成到他们的生产流程中的游戏开发人员交谈时,最令人兴奋的是时间和成本的大幅减少。 一位开发人员告诉我们,他们为单个图像生成概念艺术的时间从开始到完成已从 3 周减少到一个小时:减少了 120 比 1。 我们相信在整个生产流程中也可能实现类似的节省。</p>\n\n<p>需要明确的是,艺术家没有被取代的危险。 这确实意味着艺术家不再需要自己完成所有工作:他们现在可以设定最初的创意方向,然后将大部分耗时和技术执行交给人工智能。 在这方面,他们就像手绘动画早期的赛璐珞画家,技艺高超的“墨水工”画出动画的轮廓,然后成本较低的“画家”大军会完成耗时的绘画工作。 动画 cels,填充线条。 它是游戏创建的“自动完成”。</p>\n\n<h5 id=\"5我们还处于这场革命的初级阶段很多实践还需要完善\">5、我们还处于这场革命的初级阶段,很多实践还需要完善</h5>\n\n<p>尽管最近很兴奋,但我们仍处于起跑线上。 在我们弄清楚如何将这项新技术用于游戏的过程中,还有大量的工作要做,并且将为迅速进入这一新领域的公司创造巨大的机会。</p>\n\n<h4 id=\"二预测\">二、预测</h4>\n\n<p>鉴于这些假设,以下是对游戏行业如何转变的一些预测:</p>\n\n<h5 id=\"1学习如何有效地使用生成式人工智能将成为一种有市场价值的技能\">1、学习如何有效地使用生成式人工智能将成为一种有市场价值的技能</h5>\n\n<p>我们已经看到一些实验者比其他人更有效地使用生成式人工智能。 要充分利用这项新技术,需要使用各种工具和技术,并了解如何在它们之间灵活运用。 我们预测这将成为一种适销对路的技能,将艺术家的创意视野与程序员的技术技能相结合。</p>\n\n<p>克里斯·安德森 (Chris Anderson) 有句名言:“每一次富足都会造成新的稀缺。” 随着内容变得丰富,我们相信最短缺的是知道如何使用 AI 工具最有效地协作和工作的艺术家。</p>\n\n<p>例如,将生成式 AI 用于制作艺术品面临着特殊的挑战,包括:</p>\n\n<ul>\n <li>连贯性。 对于任何生产资产,您都需要能够在以后对资产进行更改或编辑。 使用 AI 工具,这意味着需要能够使用相同的提示重现资产,这样您就可以进行更改。这可能很棘手,因为相同的提示可能会产生截然不同的结果。</li>\n <li>风格。 给定游戏中的所有艺术都具有一致的风格很重要——这意味着您的工具需要根据您给定的风格进行培训或以其他方式绑定。</li>\n</ul>\n\n<h5 id=\"2降低壁垒将带来更多的冒险精神和创造性探索\">2、降低壁垒将带来更多的冒险精神和创造性探索</h5>\n\n<p>我们可能很快就会进入游戏开发的新“黄金时代”,在这个时代,较低的进入门槛会导致更多创新和创意游戏的爆发。 不仅因为较低的制作成本导致较低的风险,还因为这些工具释放了为更广泛的受众创建高质量内容的能力。 这导致下一个预测……</p>\n\n<h5 id=\"3人工智能辅助的微游戏工作室兴起\">3、人工智能辅助的「微游戏工作室」兴起</h5>\n\n<p>借助生成式 AI 工具和服务,我们将开始看到由只有 1 或 2 名员工的微型“微型工作室”制作出更多可行的商业游戏。 成立小型独立游戏工作室的想法并不新鲜——热门游戏 Among Us 是由只有 5 名员工的 Innersloth 工作室开发的——但这些小型工作室可以开发的游戏的规模和规模将会增长。 这将导致……</p>\n\n<h5 id=\"4每年发行的游戏数量增加\">4、每年发行的游戏数量增加</h5>\n\n<p>Unity 和 Roblox 的成功表明,提供强大的创意工具可以打造更多游戏。 生成式 AI 将进一步降低门槛,创造更多的游戏。 该行业已经面临发现挑战——仅去年一年就有超过 10,000 款游戏被添加到 Steam——这将给发现带来更大的压力。 然而,我们也会看到……</p>\n\n<h5 id=\"5生成式-ai-之前不可能创建的新游戏类型\">5、生成式 AI 之前不可能创建的新游戏类型</h5>\n\n<p>我们将看到新的游戏类型的发明,如果没有生成式 AI,这些游戏类型根本不可能实现。 我们已经谈过麦克风rosoft 的飞行模拟器,但将会有依赖于实时生成新内容的全新类型的发明。</p>\n\n<p>考虑一下 Spellbrush 的 Arrowmancer。 这是一款角色扮演游戏,以 AI 创建的角色为特色,提供几乎无限的新游戏玩法。</p>\n\n<p>我们还知道另一家游戏开发商正在使用 AI 让玩家创建自己的游戏内头像。 以前他们有一组手绘的头像图像,玩家可以混合搭配这些图像来创建他们的头像——现在他们完全抛弃了这一点,只是简单地根据玩家的描述生成头像图像。 让玩家通过 AI 生成内容比让玩家从头开始上传自己的内容更安全,因为可以训练 AI 避免创建令人反感的内容,同时仍然给玩家更大的主人翁感。</p>\n\n<h5 id=\"6价值将归于行业特定的人工智能工具而不仅仅是基础模型\">6、价值将归于行业特定的人工智能工具,而不仅仅是基础模型</h5>\n\n<p>围绕 Stable Diffusion 和 Midjourney 等基础模型的兴奋和热议正在产生令人瞠目结舌的估值,但新研究的持续涌入确保了随着新技术的改进,新模型将会出现和消失。 考虑 3 种流行的生成式 AI 模型的网站搜索流量:Dall-E、Midjourney 和 Stable Diffusion。 每个新模型都会成为人们关注的焦点。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-6.jpg\" alt=\"image\" /></p>\n\n<p>另一种方法可能是构建行业一致的工具套件,专注于特定行业的生成 AI 需求,深入了解特定受众,并充分集成到现有的生产管道(例如 Unity 或 Unreal 游戏)。</p>\n\n<p>一个很好的例子是 Runway,它通过视频编辑、绿屏移除、修复和运动跟踪等人工智能辅助工具来满足视频创作者的需求。 像这样的工具可以建立特定的受众并从中获利,随着时间的推移添加新的模型。 我们还没有看到像 Runway 这样的游戏套件出现,但我们知道这是一个积极发展的空间。</p>\n\n<h5 id=\"7法律挑战来了\">7、法律挑战来了</h5>\n\n<p>所有这些生成式 AI 模型的共同点是它们是使用海量内容数据集进行训练的,这些数据集通常是通过抓取互联网本身创建的。 例如,Stable Diffusion 接受了超过 50 亿个图像/标题对的训练,这些图像/标题对是从网络上抓取的。</p>\n\n<p>目前这些模型声称在“合理使用”版权原则下运作,但这一论点尚未在法庭上得到明确检验。 很明显,法律挑战即将到来,这可能会改变生成人工智能的格局。</p>\n\n<p>大型工作室可能会通过建立基于他们拥有明确权利和所有权的内部内容的专有模型来寻求竞争优势。 例如,微软在这方面的地位尤其有利,目前拥有 23 个第一方工作室,在收购 Activision 后还有 7 个。</p>\n\n<h5 id=\"8节目不会像艺术内容那样受到严重破坏至少现在还没有\">8、节目不会像艺术内容那样受到严重破坏——至少现在还没有</h5>\n\n<p>软件工程是游戏开发的另一项主要成本,但正如我们 a16z Enterprise 团队的同事在他们最近的博客文章中分享的那样,艺术并没有死,它只是机器生成的,使用 AI 模型生成代码需要更多测试和 验证,因此与生成创意资产相比,生产力的提高较小。 像 Copilot 这样的编码工具可能会为工程师提供适度的性能改进,但不会产生同样的影响……至少在短期内不会。</p>\n\n<h4 id=\"三建议\">三、建议</h4>\n\n<p>基于这些预测,我们提出以下建议:</p>\n\n<h5 id=\"1现在开始探索生成式-ai\">1、现在开始探索生成式 AI</h5>\n\n<p>需要一段时间才能弄清楚如何充分利用即将到来的生成式 AI 革命的力量。 现在开始的公司以后会有优势。 我们知道有几家工作室正在进行内部实验项目,以探索这些技术如何影响制作。</p>\n\n<h5 id=\"2寻找市场地图机会\">2、寻找市场地图机会</h5>\n\n<p>我们市场地图的某些部分已经非常拥挤,例如动画或语音与对话,但其他领域则非常开放。 我们鼓励对这一领域感兴趣的企业家将精力集中在尚未探索的领域,例如“游戏跑道”。</p>\n\n<h3 id=\"part-2市场地图\">Part 2、市场地图</h3>\n\n<h4 id=\"一市场现状\">一、市场现状</h4>\n\n<p>我们已经创建了一个市场地图来捕获我们在每个类别中发现的公司列表,我们在这些类别中看到生成 AI 影响游戏。 这篇博文逐一介绍了这些类别,对其进行了更详细的解释,并重点介绍了每个类别中最令人兴奋的公司。</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-7.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"二2d-图像\">二、2D 图像</h4>\n\n<p>根据文本提示生成二维图像已经是生成式人工智能应用最广泛的领域之一。 Midjourney、Stable Diffusion 和 Dall-E 2 等工具可以从文本生成高质量的 2D 图像,并且已经在游戏生命周期的多个阶段进入游戏制作。</p>\n\n<h5 id=\"1概念艺术\">1、概念艺术</h5>\n\n<p>生成式 AI 工具非常擅长“构思”或帮助非艺术家(如游戏设计师)快速探索概念和想法以生成概念图,这是一个关键部分的生产过程。 例如,一个工作室(保持匿名)正在使用其中的几个工具来从根本上加快他们的概念艺术过程,只需要一天就可以创建一个图像,而以前需要长达 3 周的时间。</p>\n\n<ul>\n <li>首先,他们的游戏设计师使用 Midjourney 探索不同的想法并生成他们觉得鼓舞人心的图像。</li>\n <li>这些被移交给专业的概念艺术家,他们将它们组装在一起并在结果上绘画以创建一个单一的连贯图像 - 然后将其输入到 Stable Diffusion 中以创建一系列变化。</li>\n <li>他们讨论这些变化,选择一个,手动绘制一些编辑——然后重复这个过程,直到他们对结果满意为止。</li>\n <li>在那个阶段,最后一次将此图像传回 Stable Diffusion 以“升级”它以创建最终的艺术作品。</li>\n</ul>\n\n<h5 id=\"2二维制作艺术\">2、二维制作艺术</h5>\n\n<p>一些工作室已经在尝试使用相同的工具来制作游戏中的艺术品。 例如,这里有一篇来自 Albert Bozesan 的精彩教程,介绍如何使用 Stable Diffusion 创建游戏中的 2D 资产。</p>\n\n<h4 id=\"三3d-图稿\">三、3D 图稿</h4>\n\n<p>3D 资产是所有现代游戏以及即将到来的元宇宙的基石。 虚拟世界或游戏关卡本质上只是 3D 资产的集合,经过放置和修改以填充环境。 然而,创建 3D 资产比创建 2D 图像更复杂,并且涉及多个步骤,包括创建 3D 模型和添加纹理和效果。 对于动画角色,它还涉及创建内部“骨架”,然后在该骨架之上创建动画。</p>\n\n<p>我们看到几家不同的初创公司在这个 3D 资产创建过程的每个阶段都在努力,包括模型创建、角色动画和关卡构建。 然而,这还不是一个已解决的问题——还没有任何解决方案准备好完全集成到生产中。</p>\n\n<h5 id=\"13d资产\">1、3D资产</h5>\n\n<p>试图解决 3D 模型创建问题的初创公司包括 Kaedim、Mirage 和 Hypothetic。 更大的公司也在关注这个问题,包括 Nvidia 的 Get3D 和 Autodesk 的 ClipForge。 Kaedim 和 Get3d 专注于图像到 3D; ClipForge 和 Mirage 专注于文本到 3D,而 Hypothetic 对文本到 3D 搜索以及图像到 3D 都感兴趣。</p>\n\n<h5 id=\"23d-纹理\">2、3D 纹理</h5>\n\n<p>3D 模型的逼真度取决于应用于网格的纹理或材料。 决定将哪种长满苔藓、风化的石头纹理应用于中世纪城堡模型可以完全改变场景的外观和感觉。 纹理包含关于光如何对材料做出反应的元数据(即粗糙度、光泽度等)。 允许艺术家根据文本或图像提示轻松生成纹理对于提高创作过程中的迭代速度非常有价值。 几个团队正在寻求这个机会,包括 BariumAI、Ponzu 和 ArmorLab。</p>\n\n<h5 id=\"3动画\">3、动画</h5>\n\n<p>创建出色的动画是游戏创建过程中最耗时、最昂贵且最需要技巧的部分之一。 一种降低成本并创建更逼真的动画的方法是使用动作捕捉,您可以让演员或舞者穿上动作捕捉服,并记录他们在配备特殊仪器的动作捕捉舞台上的移动。</p>\n\n<p>我们现在看到了可以直接从视频中捕捉动画的生成式 AI 模型。 这样效率更高,因为它不再需要昂贵的动作捕捉装置,还因为这意味着您可以从现有视频中捕捉动画。 这些模型的另一个令人兴奋的方面是,它们还可以用于对现有动画应用过滤器,例如让它们看起来喝醉了、老了或开心了。 进入这一领域的公司包括 Kinetix、DeepMotion、RADiCAL、Move Ai 和 Plask。</p>\n\n<h5 id=\"4关卡设计和世界建设\">4、关卡设计和世界建设</h5>\n\n<p>游戏创作中最耗时的一个方面是构建游戏世界,生成式 AI 应该非常适合这项任务。 Minecraft、No Man’s Sky 和 Diablo 等游戏已经以使用程序技术生成关卡而闻名,其中关卡是随机创建的,每次都不同,但遵循关卡设计师制定的规则。 新的 Unreal 5 游戏引擎的一大卖点是其用于开放世界设计的程序工具集,例如植被放置。</p>\n\n<p>我们已经看到该领域的一些举措,例如 Promethean、MLXAR 或 Meta 的 Builder Bot,并且认为生成技术在很大程度上取代程序技术只是时间问题。 该领域的学术研究已经有一段时间了,包括 Minecraft 的生成技术或 Doom 的关卡设计。</p>\n\n<p>期待用于关卡设计的生成式 AI 工具的另一个令人信服的理由是能够创建不同风格的关卡和世界。 你可以想象在 1920 年的纽约拍板时代要求工具生成一个世界,对比反乌托邦的银翼杀手式未来,对比托尔金式的幻想世界。</p>\n\n<p>以下概念是由 Midjourney 使用提示“a game level in the st是的……”</p>\n\n<p><img src=\"/img/src/2023-01-12-generative-ai-revolution-in-games-8.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"四声音\">四、声音</h4>\n\n<p>声音和音乐是游戏体验的重要组成部分。 我们开始看到公司使用 Generative AI 来生成音频,以补充图形方面已经发生的工作。</p>\n\n<h5 id=\"1声音特效\">1、声音特效</h5>\n\n<p>音效是 AI 有吸引力的开放领域。 已有学术论文探索使用 AI 在电影中生成「foley」(例如脚步声)的想法,但游戏中的商业产品还很少。</p>\n\n<p>我们认为这只是时间问题,因为游戏的交互性使其成为生成式 AI 的明显应用,既可以在制作过程中创建静态音效(「激光枪声,星球大战风格」),又 在运行时创建实时交互式音效。</p>\n\n<p>考虑为玩家角色生成脚步声这样简单的事情。 大多数游戏通过包含少量预先录制的脚步声来解决这个问题:在草地上行走、在砾石上行走、在草地上奔跑、在砾石上奔跑等。生成和管理这些声音很乏味,并且在运行时听起来重复且不真实。</p>\n\n<p>更好的方法是实时生成拟音效果的 AI 模型,它可以动态生成适当的音效,每次都略有不同,对游戏中的参数(如地面、角色重量、 步态、鞋类等</p>\n\n<h5 id=\"2音乐\">2、音乐</h5>\n\n<p>音乐一直是游戏的挑战。 这很重要,因为它可以帮助设定情绪基调,就像在电影或电视中一样,但由于游戏可以持续数百甚至数千小时,它很快就会变得重复或烦人。 此外,由于游戏的互动性,音乐可能很难在任何给定时间精确匹配屏幕上发生的事情。</p>\n\n<p>二十多年来,自适应音乐一直是游戏音频领域的一个话题,一直追溯到微软用于创建互动音乐的「DirectMusic」系统。 DirectMusic 从未被广泛采用,主要是因为以这种格式进行创作很困难。 只有少数游戏,如 Monolith 的 No One Lives Forever,创造了真正的互动配乐。</p>\n\n<p>现在我们看到许多公司正在尝试创建 AI 生成的音乐,例如 Soundful、Musico、Harmonai、Infinite Album 和 Aiva。 虽然今天的一些工具,如 Open AI 的 Jukebox,计算密集度很高,不能实时运行,但大多数工具都可以在初始模型构建后实时运行。</p>\n\n<h5 id=\"3语音和对话\">3、语音和对话</h5>\n\n<p>有大量公司试图为游戏中的角色创造逼真的声音。 考虑到尝试通过语音合成为计算机提供声音的悠久历史,这并不奇怪。 公司包括 Sonantic、Coqui、Replica Studios、Resemble.ai、Readspeaker.ai 等等。</p>\n\n<p>使用生成式 AI 进行语音有多种优势,这在一定程度上解释了为什么这个领域如此拥挤。</p>\n\n<ul>\n <li>即时生成对话。 通常游戏中的语音是由配音演员预先录制的,但这些仅限于预先录制的录音语音。 通过生成式 AI 对话,角色可以说任何话——这意味着他们可以对玩家的行为做出充分的反应。 结合用于 NPC 的更智能的 AI 模型(不在本博客的范围内,但现在是一个同样令人兴奋的创新领域),对玩家完全反应的游戏的承诺即将到来。</li>\n <li>角色扮演。 许多玩家想扮演与他们在现实世界中的身份几乎没有相似之处的奇幻角色。 然而,一旦玩家用自己的声音说话,这种幻想就会破灭。 使用与玩家头像相匹配的生成声音可以保持这种错觉。</li>\n <li>控制。 生成语音时,您可以控制声音的细微差别,如音色、音调变化、情感共鸣、音素长度、重音等。</li>\n <li>本土化。 允许将对话翻译成任何语言并以相同的声音说出来。 像 Deepdub 这样的公司专门专注于这个利基市场。</li>\n</ul>\n\n<h4 id=\"五npc-或玩家角色\">五、NPC 或玩家角色</h4>\n\n<p>许多初创公司正在考虑使用生成式 AI 来创建可以与之互动的可信角色,部分原因是这是一个在游戏之外具有如此广泛适用性的市场,例如虚拟助理或接待员。</p>\n\n<p>创造可信角色的努力可以追溯到 AI 研究的开端。 事实上,经典的人工智能“图灵测试”的定义是,人类应该无法区分与人工智能和人类的聊天对话。</p>\n\n<p>目前,有数百家公司在构建通用聊天机器人,其中许多由类似 GPT-3 的语言模型提供支持。 少数人专门尝试构建以娱乐为目的的聊天机器人,例如试图构建虚拟朋友的 Replika 和 Anima。 正如电影《她》中探讨的那样,与虚拟女友约会的概念可能比您想象的更接近。</p>\n\n<p>我们现在看到了这些聊天机器人平台的下一次迭代,例如 Charisma.ai、Convai.com 或 Inworld.ai,旨在为完全撕裂提供动力创建具有情感和代理的 3D 角色,以及允许创作者为这些角色设定目标的工具。 如果他们要融入游戏或在推动情节发展方面有一个叙事位置,而不是纯粹的门面装饰,这一点就很重要。</p>\n\n<h4 id=\"六多合一平台\">六、多合一平台</h4>\n\n<p>Runwayml.com 是最成功的生成式 AI 工具之一,因为它在一个软件包中汇集了广泛的创作者工具套件。 目前还没有这样的视频游戏平台,我们认为这是一个被忽视的机会。 我们很乐意投资具有以下特点的解决方案:</p>\n\n<ul>\n <li>涵盖整个生产过程的全套人工智能生成工具。 (代码、资产生成、纹理、音频、描述等)</li>\n <li>与 Unreal 和 Unity 等流行游戏引擎紧密集成。</li>\n <li>旨在适应典型的游戏制作流程。</li>\n</ul>\n\n<h4 id=\"七结论\">七、结论</h4>\n\n<p>对于游戏创作者来说,这是一个不可思议的时刻! 部分归功于这篇博文中描述的工具,生成构建游戏所需的内容从未如此简单——即使您的游戏与整个地球一样大!</p>\n\n<p>甚至有一天可以想象一款完全个性化的游戏,完全根据玩家的需求为玩家打造。 这在科幻小说中已经存在很长时间了——比如《安德的游戏》中的「AI 智力游戏」,或者《星际迷航》中的全息甲板。 但是随着这篇博文中描述的工具发展得如此之快,不难想象这一现实指日可待。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>【摘录】通向 AGI 之路:大型语言模型(LLM)技术精要</h2>\t\t\n\t<time datetime=\"2023-01-08T18:13:09+00:00\" class=\"by-line\">08 Jan 2023, 杭州 | 麦克船长 | 总计 40590 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-1.jpeg\" alt=\"image\" /></p>\n\n<p>原文链接:<a href=\"https://zhuanlan.zhihu.com/p/597586623\">https://zhuanlan.zhihu.com/p/597586623</a></p>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一潮流之巅nlp-研究范式的转换\" id=\"markdown-toc-一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</a> <ul>\n <li><a href=\"#1范式转换-10从深度学习到两阶段预训练模型\" id=\"markdown-toc-1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</a> <ul>\n <li><a href=\"#11影响一中间任务的消亡\" id=\"markdown-toc-11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</a></li>\n <li><a href=\"#12影响二不同研究方向技术路线的统一\" id=\"markdown-toc-12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</a></li>\n </ul>\n </li>\n <li><a href=\"#2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\" id=\"markdown-toc-2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</a> <ul>\n <li><a href=\"#21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\" id=\"markdown-toc-21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</a></li>\n </ul>\n </li>\n <li><a href=\"#影响一让-llm-适配人的新型交互接口\" id=\"markdown-toc-影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</a></li>\n <li><a href=\"#影响二很多-nlp-子领域不再具备独立研究价值\" id=\"markdown-toc-影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</a></li>\n <li><a href=\"#影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\" id=\"markdown-toc-影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</a></li>\n </ul>\n </li>\n <li><a href=\"#二学习者从无尽数据到海量知识\" id=\"markdown-toc-二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</a> <ul>\n <li><a href=\"#1求知之路llm-学到了什么知识\" id=\"markdown-toc-1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</a></li>\n <li><a href=\"#2记忆之地llm-如何存取知识\" id=\"markdown-toc-2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</a></li>\n <li><a href=\"#3知识涂改液如何修正-llm-里存储的知识\" id=\"markdown-toc-3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</a></li>\n </ul>\n </li>\n <li><a href=\"#三规模效应当-llm-越来越大时会发生什么\" id=\"markdown-toc-三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</a></li>\n <li><a href=\"#四人机接口从-in-context-learning-到-instruct-理解\" id=\"markdown-toc-四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</a> <ul>\n <li><a href=\"#1神秘的-in-context-learning\" id=\"markdown-toc-1神秘的-in-context-learning\">1、神秘的 In Context Learning</a></li>\n <li><a href=\"#2神奇的-instruct-理解\" id=\"markdown-toc-2神奇的-instruct-理解\">2、神奇的 Instruct 理解</a></li>\n <li><a href=\"#3in-context-learning-和-instruct-的联系\" id=\"markdown-toc-3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</a></li>\n </ul>\n </li>\n <li><a href=\"#五智慧之光如何增强-llm-的推理能力\" id=\"markdown-toc-五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</a> <ul>\n <li><a href=\"#1基于-prompt-的方法\" id=\"markdown-toc-1基于-prompt-的方法\">1、基于 Prompt 的方法</a></li>\n <li><a href=\"#2代码预训练增强-llm-推理能力\" id=\"markdown-toc-2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</a></li>\n <li><a href=\"#3关于-llm-推理能力的思考\" id=\"markdown-toc-3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</a></li>\n </ul>\n </li>\n <li><a href=\"#六未来之路llm-研究趋势及值得研究的重点方向\" id=\"markdown-toc-六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</a> <ul>\n <li><a href=\"#探索-llm-模型的规模天花板\" id=\"markdown-toc-探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</a></li>\n <li><a href=\"#增强-llm-的复杂推理能力\" id=\"markdown-toc-增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</a></li>\n <li><a href=\"#llm-纳入-nlp-之外更多其它研究领域\" id=\"markdown-toc-llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</a></li>\n <li><a href=\"#更易用的人和-llm-的交互接口\" id=\"markdown-toc-更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</a></li>\n <li><a href=\"#建设高难度的综合任务评测数据集\" id=\"markdown-toc-建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</a></li>\n <li><a href=\"#高质量数据工程\" id=\"markdown-toc-高质量数据工程\">高质量数据工程</a></li>\n <li><a href=\"#超大-llm-模型-transformer-的稀疏化\" id=\"markdown-toc-超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</a></li>\n </ul>\n </li>\n <li><a href=\"#七取经之路复刻-chatgpt-时要注意些什么\" id=\"markdown-toc-七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</a></li>\n <li><a href=\"#八chatgpt为什么是-openai\" id=\"markdown-toc-八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</a></li>\n</ul>\n\n<p>ChatGPT 出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型(LLM,Large Language Model)效果能好成这样;惊醒是顿悟到我们对LLM的认知及发展理念,距离世界最先进的想法,差得有点远。我属于既惊喜又惊醒的那一批,也是典型的中国人,中国人善于自我反思,于是开始反思,而这篇文章正是反思的结果。</p>\n\n<p>实话实说,国内在 LLM 模型相关技术方面,此刻,距离最先进技术的差距进一步加大了。技术领先或技术差距这事情,我觉得要动态地以发展的眼光来看。<strong>在 Bert 出现之后的一到两年间,其实国内在这块的技术追赶速度还是很快的,也提出了一些很好的改进模型,差距拉开的分水岭应该是在 GPT 3.0 出来之后,也就是 2020 年年中左右</strong>。在当时,其实只有很少的人觉察到:GPT 3.0 它不仅仅是一项具体的技术,其实体现的是 LLM 应该往何处去的一个发展理念。自此之后,差距拉得越来越远,ChatGPT 只是这种发展理念差异的一个自然结果。所以,我个人认为,抛开是否有财力做超大型 LLM 这个因素,如果单从技术角度看,差距主要来自于对 LLM 的认知以及未来应往何处去的发展理念的不同。</p>\n\n<p>国内被国外技术甩得越来越远,这个是事实,不承认也不行。前阵子网上很多人担忧说国内 AI 现在处于「危急存亡之秋」,我觉得倒也不至于这么严重。君不见,这个世界上,具备这么超前眼光的只有 OpenAI 一家吗?<strong>包括 Google 在内,其实对于 LLM 发展理念的理解,明显都落后 OpenAI 一个身位。现实是 OpenAI 表现过于优秀,把所有人都甩开了,不仅仅是国内</strong>。</p>\n\n<p>我觉得,OpenAI 对 LLM 在理念及相关技术方面,领先国外的 Google、DeepMind 大约半年到一年的时间,领先国内大概两年左右的时间。在 LLM 这个事情上,感觉梯队很明显,Google 应该是排在第二位,最能体现 Google 技术眼光的是 PaLM 和 Pathways,推出时间大概在 22 年 2 月到 4 月间,同一时期,OpenAI 推出的却是 InstructGPT,从这里就可以看出 Google 和 OpenAI 的差距了,至于为何这么说,你看了我后面的正文后大概能理解。DeepMind 之前的重心一直在强化学习攻克游戏和 AI for science 这些方面,切入LLM 其实很晚,应该是21 年才开始重视这个方向,目前也处于追赶状态。Meta 就更不用说了,重心一直不在 LLM 上,目前感觉也发力开始追赶。这还是目前做得最好的一批机构,尚且如此,更何况国内呢?我觉得情有可原。至于 OpenAI 关于 LLM 的理念是什么,我在本文的最后一部分,会谈谈我的认知。</p>\n\n<p>本文梳理自 GPT 3.0 出现之后的主流 LLM 技术,能够让您对 LLM 领域的技术脉络,LLM 技术发展过程中出现过的不同发展理念,乃至未来可能的发展趋势,有比较清晰的认知。当然,很多地方讲的内容是我个人看法,有很大的主观性,错漏难免,所以还请谨慎参考。</p>\n\n<p>本文试图回答下面一些问题:ChatGPT 是否带来了 NLP 乃至 AI 领域的研究范式转换?如果是,那会带来怎样的影响?LLM 从海量数据中学到了什么知识?LLM 又是如何存取这些知识的?随着LLM规模逐步增大,会带来什么影响?什么是 In Context Learning?为什么它是一项很神秘的技术?它和 Instruct 又是什么关系?LLM 具备推理能力吗?思维链 CoT 又是怎么做的?等等,相信看完,能让您对这些问题有一个答案。</p>\n\n<p>首先,在谈 LLM 技术现状前,先宏观地谈下我心目中的研究范式转换问题。这样,我们才能「先见森林,再见树木」,对具体技术为何会是如此变化有个更清晰的认知。</p>\n\n<h2 id=\"一潮流之巅nlp-研究范式的转换\">一、潮流之巅:NLP 研究范式的转换</h2>\n\n<p>如果我们把时间线往前拉得更长一些,回到 NLP 领域的深度学习时代,在更长时间窗口内观察技术变迁及其影响,可能会更容易看清其中的一些关键节点。我个人认为,在最近 10 年来NLP领域的技术发展过程中,可能存在两次大的研究范型转换。</p>\n\n<h3 id=\"1范式转换-10从深度学习到两阶段预训练模型\">1、范式转换 1.0:从深度学习到两阶段预训练模型</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在深度学习引入 NLP 领域(2013 年左右),到 GPT 3.0 出现之前(2020 年 5 月左右)。</p>\n\n<p>在 Bert 和 GPT 模型出现之前,NLP 领域流行的技术是深度学习模型,而 NLP 领域的深度学习,主要依托于以下几项关键技术:以大量的改进 LSTM 模型及少量的改进 CNN 模型作为典型的特征抽取器;以 Sequence to Sequence(或叫 encoder-decoder 亦可)+ Attention 作为各种具体任务典型的总体技术框架。</p>\n\n<p>在这些核心技术加持下,NLP 领域深度学习的主要研究目标,如果归纳一下,是如何有效增加模型层深或模型参数容量。就是说,怎么才能往 encoder 和 decoder 里不断叠加更深的 LSTM 或 CNN 层,来达成增加层深和模型容量的目标。这种努力,尽管确实不断增加了模型层深,但是从解决具体任务的效果角度看,总体而言,不算很成功,或者说和非深度学习方法相比,带来的优势不算大。</p>\n\n<p>深度学习之所以不够成功,我认为主要原因来自于两个方面:一方面是某个具体任务有限的训练数据总量。随着模型容量的增加,需要靠更大量的训练数据来支撑,否则即使你能把深度做起来,任务效果也做不上去。而在预训练模型出现之前,很明显这是 NLP 研究领域一个严重问题;另外一个方面是 LSTM/CNN 特征抽取器,表达能力不够强。意思是就算给你再多的数据也没用,因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因,阻碍了深度学习在 NLP 领域的成功突围。</p>\n\n<p>Bert / GPT 这两个预训练模型的出现,无论在学术研究角度看,还是工业应用角度来看,都代表了 NLP 领域的一个技术飞跃,并带来了整个领域研究范式的转换。这种范式转换带来的影响,体现在两个方面:首先,是部分 NLP 研究子领域的衰退乃至逐步消亡;其次,NLP 不同子领域的技术方法和技术框架日趋统一,在 Bert 出现后一年左右,技术栈基本收敛到两种技术模式中。关于这两点,我们分头来谈。</p>\n\n<h4 id=\"11影响一中间任务的消亡\">1.1、影响一:中间任务的消亡</h4>\n\n<p>NLP 是一个宏观研究领域的统称,里面有五花八门具体的子领域与子方向,如果仔细分析,从任务的性质角度,可以把这些任务分成两大类:一类可以叫做「中间任务」,一类可以称为「最终任务」。</p>\n\n<p>典型的中间任务包括:中文分词、词性标注、NER、句法分析、指代消解、语义 Parser 等,这类任务一般并不解决应用中的实际需求,大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的,比如几乎没有需求说,我要一个句法 Parser,把这个句子的句法分析树给用户看看,用户不需要看到这些NLP的中间阶段处理结果,他只关心某个具体任务你有没有干好。「最终任务」包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等,有很多。这类任务的特点是每个子领域都解决某个实际需求,任务结果基本能直接呈现给用户,比如用户确实存在给你一句英文,告诉他中文是什么的需求。</p>\n\n<p>按理说,「中间任务」就不应该出现,而之所以会存在,这是 NLP 技术发展水平不够高的一种体现。在技术发展早期阶段,因为当时的技术相对落后,很难一步做好有难度的最终任务。比如机器翻译,早期技术要做好机器翻译是很困难的,于是科研人员就把难题分而治之,分解成分词、词性标注、句法分析等各种中间阶段,先把每个中间阶段做好,然后再拼起来完成最终任务,这也是没办法的事情。</p>\n\n<p>但是自从 Bert/GPT 出现之后,其实就没有必要做这些中间任务了,因为通过大量数据的预训练,Bert/GPT 已经把这些中间任务作为语言学特征,吸收到了 Transformer 的参数里,此时我们完全可以端到端地直接解决那些最终任务,而无须对这种中间过程专门建模。这里可能争议最大的是中文分词,其实道理也是一样的,哪些字应该组成一个词,这个其实你不用管,让 LLM 自己当特征去学就行了,只要对于解决任务有帮助,它自然会去学该学的合理分词方式,也未必一定要和我们人类理解的分词规则相同。</p>\n\n<p>基于以上认知,其实在Bert/GPT一出现,你就应该得出这类NLP的中间阶段的任务,会逐步退出历史舞台这个结论。</p>\n\n<h4 id=\"12影响二不同研究方向技术路线的统一\">1.2、影响二:不同研究方向技术路线的统一</h4>\n\n<p>在说明具体影响前,我们先讨论下另外一种 NLP 任务划分方式,这对于理解后面内容有帮助。如果对「最终任务」进一步进行分类,又大致可以分为两大不同类型的任务:自然语言理解类任务和自然语言生成类任务。如果排除掉「中间任务」的话,典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等,这种任务本质上都是分类任务,就是说输入一个句子(文章),或者两个句子,模型参考所有输入内容,最后给出属于哪个类别的判断。自然语言生成也包含很多 NLP 研究子方向,比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本,对应地,模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上。</p>\n\n<p>自从 Bert/GPT 模型诞生后,出现了明显的技术统一趋向。首先,NLP 中不同的子领域,其特征抽取器都逐渐从 LSTM/CNN 统一到 Transformer 上。其实,自Bert公开后不久,就应该意识到,这必然会成为技术趋势。而且,目前 Transformer 不仅统一了 NLP 诸多领域,也正在逐步地替换图像处理各种任务中被广泛使用的 CNN 等其它模型的进程之中,类似的,多模态模型目前也基本都采用了 Transformer 模型。这种Transformer从NLP出发,攻城略地逐步统一AI越来越多领域的趋势,起始于 2020 年底出现的 Vision Transformer (ViT) ,之后蓬勃发展,到目前已大获成功,且其继续向更多领域拓展的势头会越来越迅猛。</p>\n\n<p>其次,大多数 NLP 子领域的研发模式切换到了两阶段模式:模型预训练阶段 + 应用微调(Fine-tuning)或应用 Zero/Few Shot Prompt 模式。更准确地说,NLP 各种任务其实收敛到了两个不同的预训练模型框架里:对于自然语言理解类任务,其技术体系统一到了以 Bert 为代表的「双向语言模型预训练 + 应用 Fine-tuning」模式;而对于自然语言生成类任务,其技术体系则统一到了以GPT 2.0 为代表的「自回归语言模型(即从左到右单向语言模型)+ Zero/Few Shot Prompt」模式。至于为何会分化成两条技术路线,有其必然性,关于这点我们放在后面解释。</p>\n\n<p>这两种模式,看似比较相像,但其背后蕴含了迥异的发展思路,也会导向不同的未来发展方向。不过遗憾的是,我们中的绝大多数人,在当时都低估了GPT 这条发展路线的潜力,而把视觉中心聚焦到了Bert这种模式上。</p>\n\n<h3 id=\"2范式转换-20-从预训练模型走向通用人工智能-agiartificial-general-intelligence\">2、范式转换 2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)</h3>\n\n<p>这个范式转换所涵盖的时间范围,大致在 GPT 3.0 出现之后(20 年 6 月左右),一直到目前为止,我们应该正处于这个范式转换过程中。</p>\n\n<p>ChatGPT 是触发这次范型转换的关键节点,但是在 InstructGPT 出现之前,其实 LLM 处于这次范式转换前的一个过渡期。</p>\n\n<h4 id=\"21过渡期以-gpt-30-为代表的自回归语言模型--prompting模式占据统治地位\">2.1、过渡期:以 GPT 3.0 为代表的「自回归语言模型 + Prompting」模式占据统治地位</h4>\n\n<p>前面说过,在预训练模型发展的早期,技术框架收敛到了 Bert 模式和 GPT 模式这两种不同的技术范型,而且人们普遍更看好 Bert 模式一些,相当多数的后续技术改进,都是沿着 Bert 那条路走的。但是,随着技术的继续发展,你会发现,目前规模最大的 LLM 模型,几乎清一色都是类似 GPT 3.0 这种「自回归语言模型 + Prompting」模式的,比如 GPT-3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA 等,没有例外。为什么会这样呢?背后一定有其必然性,我认为可能主要源于两个原因。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-2.jpeg\" alt=\"image\" /></p>\n\n<p>首先,Google 的 T5 模型,在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示,标为红色的是个文本分类问题,黄色的是判断句子相似性的回归或分类问题,这都是典型的自然语言理解问题。在 T5 模型里,这些自然语言理解问题在输入输出形式上和生成问题保持了一致,也就是说,可以把分类问题转换成让 LLM 模型生成对应类别的字符串,这样理解和生成任务在表现形式就实现了完全的统一。</p>\n\n<p>这说明自然语言生成任务,在表现形式上可以兼容自然语言理解任务,若反过来,则很难做到这一点。这样的好处是:同一个 LLM 生成模型,可以解决几乎所有 NLP 问题。而如果仍然采取 Bert 模式,则这个 LLM 模型无法很好处理生成任务。既然这样,我们当然倾向于使用生成模型,这是一个原因。</p>\n\n<p>第二个原因,如果想要以零示例提示语(zero shot prompting)或少数示例提示语(few shot prompting)的方式做好任务,则必须要采取 GPT 模式。现在已有研究(参考:<a href=\"https://arxiv.org/pdf/2205.11726\">《On the Role of Bidirectionality in Language Model Pre-Training》</a>)证明:如果是以 fine-tuning 方式解决下游任务,Bert模式的效果优于 GPT 模式;若是以 zero shot / few shot prompting 这种模式解决下游任务,则GPT模式效果要优于 Bert 模式。这说明了,生成模型更容易做好 zero shot/few shot prompting 方式的任务,而Bert模式以这种方式做任务,是天然有劣势的。这是第二个原因。</p>\n\n<p>但是问题来了:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?要解释清楚这个问题,我们首先需要搞清楚另外一个问题:什么样的 LLM 模型,对我们是最理想的?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-3.jpeg\" alt=\"image\" /></p>\n\n<p>上图展示了一个理想的 LLM 该有的样子。首先,LLM 应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它,它应该能够自动从中学习到里面包含的所有知识点,学习过程不需要人的介入,并且能灵活应用所学知识,来解决实际问题。因为数据是海量的,要吸收所有知识,就要非常多的模型参数来存储知识,所以这个模型必然会是一个巨无霸模型。</p>\n\n<p>其次,LLM 应该能解决 NLP 任何子领域的问题,而不仅支持有限领域,甚至它应该可以响应 NLP 之外其它领域的问题,最好是任意领域的问题都能得到很好地回答。</p>\n\n<p>再者,当我们使用 LLM 解决某个具体领域问题的时候,应该用我们人类习惯的表达方式,就是说LLM应该理解人类的命令。这体现出让 LLM 适配人,而不是反过来,让人去适配 LLM 模型。人适配 LLM 的典型例子,比如绞尽脑汁去尝试各种不同的 prompt,以试图找到好的提示语,才能很好地解决手头问题。关于这点,上图在人类和 LLM 交互的接口层,举了几个例子,说明什么是好的人使用 LLM 模型的接口形式。</p>\n\n<p>看完这个理想中的 LLM,我们再回头解释上面遗留的问题:为什么我们要追求 zero shot / few shot prompting 这种方式来做任务呢?有两个原因。</p>\n\n<p>第一,这个 LLM 模型规模必然非常巨大,有能力作出这个模型,或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人,就算你把模型开源出来,他们也无力部署这个模型,更不用说再用 Fine-tuning 这种模式去修改模型参数了。所以,我们应该追求不修正模型参数,就能让任务需求方完成任务的方式,也就是应该采取 prompt 模式完成任务,而非 Fine-tuning 模式(由此可看出,soft prompting 技术方向是违背这个发展趋势的)。模型制作方则将 LLM 作成公用服务,以 LLM as Service 的模式运行。作为服务支持方,考虑到千变万化的用户需求,所以 LLM 模型制作方更要追求让 LLM 能完成尽可能多类型的任务,这是附带的影响,也是为何超级大模型一定会追求走向AGI的现实因素。</p>\n\n<p>第二,zero shot prompting 也好,few shot prompting 也好,甚至促进LLM推理能力的思维链(CoT,Chain of Thought)Prompting 也好,就是上图中接口层中的现有技术。具体而言,zero shot prompting 的初衷,其实就是人类和 LLM 的理想接口,直接用人类所习惯的任务表述方式让 LLM 做事情,但是发现 LLM 并不能很好地理解,效果也不好。经过继续研究,转而发现:对于某项任务,如果给 LLM 几个示例,用这些示例来代表任务描述,效果会比 zero shot prompting 好,于是大家都去研究更好的 few shot prompting 技术。可以理解为,本来我们希望 LLM 能够用人类常用的命令方式来执行某个任务,但是目前技术还做不到,所以退而求其次,用这些替代技术来表达人类的任务需求。</p>\n\n<p>如果理解了上述逻辑,很容易得出如下结论:few shot prompting(也被称为In Context Learning)只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务,而且 LLM 可以理解,那么,我们肯定会毫不犹豫地抛弃这些过渡期的技术,原因很明显,用这些方法来描述任务需求,并不符合人类的使用习惯。</p>\n\n<p>这也是为何我将 GPT 3.0 + Prompting 列为过渡期技术的原因,ChatGPT 的出现,改变了这个现状,用 Instruct 取代了 Prompting,由此带来新的技术范式转换,并产生若干后续影响。</p>\n\n<h3 id=\"影响一让-llm-适配人的新型交互接口\">影响一:让 LLM 适配人的新型交互接口</h3>\n\n<p>在理想 LLM 的背景下,我们再来看 ChatGPT,能更好理解它的技术贡献。ChatGPT 应该是目前所有的现有技术里,最接近理想 LLM 的技术方法。如果归纳下 ChatGPT 最突出特点的话,我会用下面八个字「能力强大,善解人意」。</p>\n\n<p>「能力强大」这一点,我相信应该主要归功于 ChatGPT 所依托的基础 LLM GPT-3.5。因为 ChatGPT 尽管加入了人工标注数据,但是量级只有数万,这个规模的数据量,和训练 GPT 3.5 模型使用的几千亿 token 级别的数据量相比,包含的世界知识(数据中包含的事实与常识)可谓沧海一粟,几可忽略,基本不会对增强 GPT 3.5 的基础能力发挥什么作用。所以它的强大功能,应该主要来自于隐藏在背后的 GPT 3.5。GPT 3.5 对标理想 LLM 模型中的那个巨无霸模型。</p>\n\n<p>那么,ChatGPT 向 GPT 3.5 模型注入新知识了吗?应该是注入了,这些知识就包含在几万人工标注数据里,不过注入的不是世界知识,而是人类偏好知识。所谓「人类偏好」,包含几方面的含义:首先,是人类表达一个任务的习惯说法。比如,人习惯说「把下面句子从中文翻译成英文」,以此表达一个「机器翻译」的需求,但是 LLM 又不是人,它怎么会理解这句话到底是什么意思呢?你得想办法让 LLM 理解这句命令的含义,并正确执行。所以,ChatGPT 通过人工标注数据,向GPT 3.5 注入了这类知识,方便 LLM 理解人的命令,这是它“善解人意”的关键。其次,对于什么是好的回答,什么是不好的回答,人类有自己的标准,例如比较详细的回答是好的,带有歧视内容的回答是不好的,诸如此类。这是人类自身对回答质量好坏的偏好。人通过 Reward Model 反馈给 LLM 的数据里,包含这类信息。总体而言,ChatGPT 把人类偏好知识注入 GPT 3.5,以此来获得一个听得懂人话、也比较礼貌的 LLM。</p>\n\n<p>可以看出,ChatGPT 的最大贡献在于:基本实现了理想 LLM 的接口层,让 LLM 适配人的习惯命令表达方式,而不是反过来让人去适配 LLM,绞尽脑汁地想出一个能 Work 的命令(这就是 instruct 技术出来之前,prompt 技术在做的事情),而这增加了 LLM 的易用性和用户体验。是 InstructGPT / ChatGPT 首先意识到这个问题,并给出了很好的解决方案,这也是它最大的技术贡献。相对之前的 few shot prompting,它是一种更符合人类表达习惯的人和 LLM 进行交互的人机接口技术。</p>\n\n<p>而这必将启发后续的 LLM 模型,继续在易用人机接口方面做进一步的工作,让 LLM 更听话。</p>\n\n<h3 id=\"影响二很多-nlp-子领域不再具备独立研究价值\">影响二:很多 NLP 子领域不再具备独立研究价值</h3>\n\n<p>就 NLP 领域而言,这次范式转换,意味着很多目前独立存在的 NLP 研究领域,将被纳入 LLM 的技术体系,进而不再独立存在,逐步消失。经过第一次范式转换,尽管 NLP 中很多「中间任务」,继续作为独立研究领域存在不再必要,但是大多数「最终任务」,仍然是以独立研究领域存在的,只是切换成在「预训练 + fine-tuning」框架下,面对领域独有问题,陆续提出新的改进方案。</p>\n\n<p>目前研究表明,很多 NLP 任务,随着 LLM 模型规模增长,效果会大幅提升。据此,我觉得可得到如下推论:大多数某领域所谓「独有」的问题,大概率只是缺乏领域知识导致的一种外在表象,只要领域知识足够多,这个所谓领域独有的问题,就可以被很好地解决掉,其实并不需要专门针对某个具体领域问题,冥思苦想去提出专用解决方案。也许 AGI 的真相超乎意料地简单:你只要把这个领域更多的数据交给 LLM,让它自己学习更多知识即可。</p>\n\n<p>在这个背景下,同时,ChatGPT 证明了我们现在是可以直接去追求理想 LLM 模型的,那么,未来的技术发展趋势应该是:追求规模越来越大的 LLM 模型,通过增加预训练数据的多样性,来涵盖越来越多的领域,LLM 自主从领域数据中通过预训练过程学习领域知识,随着模型规模不断增大,很多问题随之得到解决。研究重心会投入到如何构建这个理想 LLM 模型,而非去解决某个领域的具体问题。这样,越来越多 NLP 的子领域会被纳入 LLM 的技术体系,进而逐步消失。</p>\n\n<p>我认为,判断某个具体领域是否该立即停止独立研究,其判断标准可采取以下两种方法,占其一即可:第一,判断某个任务,是否 LLM 的研究效果超过人类表现,对于那些 LLM 效果超过人类的研究领域,已无独立研究的必要。举个例子,GLUE 与 SuperGLUE 测试集合里的很多任务,目前 LLM 效果已超过人类表现,与这个数据集合密切相关的研究领域,其实就没有继续独立存在的必要。第二,对比两种模式的任务效果,第一种模式是用较大的领域专用数据进行 Fine-tuning,第二种是 few-shot prompting 或 instruct-based 方法。如果第二种方法效果达到或超过第一种方法,则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看,其实很多研究领域,目前 fine-tuning 效果还是占优的(因为这种模式领域训练数据量大),看似还可独立存在。但是考虑到很多任务随着模型规模增大,few shot prompting 效果持续增长,随着更大模型的出现,这个拐点很可能短期就会达到。</p>\n\n<p>如果上述猜测成立,将意味着如下残酷事实:对于很多 NLP 领域的研究人员,将面临往何处去的选择,是继续做领域独有问题呢?还是放弃这种看似前途不大的方式,转而去建设更好的LLM?如果选择转向去建设 LLM,又有哪些机构有能力、有条件去做这个事情呢?你对这个问题的回答会是什么呢?</p>\n\n<h3 id=\"影响三更多-nlp-之外的研究领域将被纳入-llm-技术体系\">影响三:更多 NLP 之外的研究领域将被纳入 LLM 技术体系</h3>\n\n<p>如果站在 AGI 的视角,参照之前描述的理想 LLM 模型,它所能完成的任务,不应局限于 NLP 领域,或某一两个学科领域,理想中的 LLM 应该是领域无关的通用人工智能模型,它现在在某一两个领域做得好,不代表只能做这些任务。ChatGPT 的出现,证明了现在这个时期,我们去追求AGI是有可行性的,而现在是抛开「领域学科」这个思维束缚的时候了。</p>\n\n<p>ChatGPT 除了展示出以流畅的对话形式解决各种 NLP 任务外,也具备强大的代码能力。很自然的,之后越来越多其它的研究领域,也会被逐步纳入 LLM 体系中,成为通用人工智能的一部分。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-4.jpeg\" alt=\"image\" /></p>\n\n<p>LLM 从 NLP 向外进行领域拓展,一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入,让LLM成为一个支持多模态输入输出的通用人机接口,典型的例子包括 DeepMind 的 Flamingo 和微软的<a href=\"https://arxiv.org/pdf/2206.06336.pdf\">《Language Models are General-Purpose Interfaces》</a>,上图展示了这种方式的概念结构。</p>\n\n<p>我的判断是无论是图像还是多模态,未来被融入 LLM 成为好用的功能,可能比我们想象的进度要慢。主要原因在于:尽管图像领域最近两年也一直在模仿 Bert 预训练的路子,尝试引入自监督学习,释放模型自主从图像数据中学习知识的能力,典型技术就是“对比学习”和 MAE,这是两条不同的技术路线。然而,从目前效果来看,尽管取得了很大的技术进步,但貌似这条路尚未走通,这体现在图像领域预训练模型应用到下游任务,带来的效果收益,远不如 Bert 或 GPT 应用在 NLP 下游任务那样显著。所以,图像预处理模型仍需深入探索,以释放图像数据的潜力,而这会迟滞它们被统一到 LLM 大模型的时间。当然,如果哪天这条路被趟通,大概率会复现NLP领域目前的局面,就是图像处理各个研究子领域可能会逐步消失,被融入到大型 LLM 中来,直接完成终端任务。</p>\n\n<p>除了图像与多模态,很明显,其它领域也会逐渐被纳入到理想 LLM 中来,这个方向方兴未艾,是具备高价值的研究主题。</p>\n\n<p>以上是我对范式转换的个人思考,接下来,我们来梳理下 GPT 3.0 之后 LLM 模型的主流技术进展。如理想 LLM 模型所示,相关的技术其实可以分为两大类;一类是关于 LLM 模型如何从数据中吸收知识,也包括模型规模增长对 LLM 吸收知识能力带来的影响;第二类是关于人如何使用 LLM 内在能力来解决任务的人机接口,包括In Context Learning 和 Instruct 两种模式。思维链(CoT)prompting 这种 LLM 推理技术,本质上也属于 In Context Learning,因为比较重要,我就把它们单独拎出来讲一下。</p>\n\n<h2 id=\"二学习者从无尽数据到海量知识\">二、学习者:从无尽数据到海量知识</h2>\n\n<p>从目前研究结果看,Transformer 是足够强大的特征抽取器,尚不需要做特别的改进。那么通过预训练过程,Transformer 学到了什么?知识是如何存取的?我们又如何修正错误知识?本节讲述这方面的研究进展。</p>\n\n<h3 id=\"1求知之路llm-学到了什么知识\">1、求知之路:LLM 学到了什么知识</h3>\n\n<p>LLM 从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,可以分为语言类知识和世界知识两大类。</p>\n\n<p>语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于 LLM 能否捕获语言知识有较长研究历史,自从 Bert 出现以来就不断有相关研究,很早就有结论,各种实验充分证明 LLM 可以学习各种层次类型的语言学知识,这也是为何使用预训练模型后,各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外,各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在 Transformer 的低层和中层,而抽象的语言知识比如语义类知识,广泛分布在 Transformer 的中层和高层结构中。</p>\n\n<p>世界知识指的是在这个世界上发生的一些真实事件(事实型知识,Factual Knowledge),以及一些常识性知识(Common Sense Knowledge)。比如「拜登是现任美国总统」、「拜登是美国人」、「乌克兰总统泽连斯基与美国总统拜登举行会晤」,这些都是和拜登相关的事实类知识;而「人有两只眼睛」、「太阳从东方升起」这些属于常识性知识。关于 LLM 模型能否学习世界知识的研究也有很多,结论也比较一致:LLM 确实从训练数据中吸收了大量世界知识,而这类知识主要分布在 Transformer 的中层和高层,尤其聚集在中层。而且,随着 Transformer 模型层深增加,能够学习到的知识数量逐渐以指数级增加(可参考<a href=\"https://arxiv.org/pdf/2106.02902.pdf\">《BERTnesia: Investigating the capture and forgetting of knowledge in BERT》</a>)。其实,你把 LLM 看作是一种以模型参数体现的隐式知识图谱,如果这么理解,我认为是一点问题也没有的。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2011.04946\">《When Do You Need Billions of Words of Pre-training Data?》</a>这篇文章研究了预训练模型学习到的知识量与训练数据量的关系,它的结论是:对于 Bert 类型的语言模型来说,只用 1000 万到 1 亿单词的语料,就能学好句法语义等语言学知识,但是要学习事实类知识,则要更多的训练数据。这个结论其实也是在意料中的,毕竟语言学知识相对有限且静态,而事实类知识则数量巨大,且处于不断变化过程中。而目前研究证明了随着增加训练数据量,预训练模型在各种下游任务中效果越好,这说明了从增量的训练数据中学到的更主要是世界知识。</p>\n\n<h3 id=\"2记忆之地llm-如何存取知识\">2、记忆之地:LLM 如何存取知识</h3>\n\n<p>由上可知,LLM 确实从数据中学到了很多语言类及世界知识。那么,对于某条具体的知识,LLM 把它存储到了哪里?又是如何提取出来的?这也是一个有意思的问题。</p>\n\n<p>显然,知识一定存储在 Transformer 的模型参数里。从 Transformer 的结构看,模型参数由两部分构成:<strong>多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中</strong>。MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点,那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-5.jpeg\" alt=\"image\" /></p>\n\n<p>但这样的定位,粒度还是太粗,无法很好回答具体某条知识是如何存储与提取的,比如「中国的首都是北京」这条知识,以三元组表达就是<北京,is-capital-of,中国>,其中「is-capital-of」代表实体间关系。<strong>这条知识它存储在 LLM 的哪里呢?</strong></p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:这些知识存哪了?我们现在有以下几点认知:<br />\n1、多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在 FFN 结构中。<br />\n2、MHA 主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点。<br />\n3、那么很容易推论出 LLM 模型的知识主体是存储在 Transformer 的 FFN 结构里。<br />\n4、一些研究达成共识:Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,比如《Transformer Feed-Forward Layers Are Key-Value Memories》</p>\n</blockquote>\n\n<p><a href=\"https://arxiv.org/pdf/2012.14913.pdf\">《Transformer Feed-Forward Layers Are Key-Value Memories》</a>给出了一个比较新颖的观察视角,它把 Transformer 的 FFN 看成存储大量具体知识的 Key-Value 存储器。如上图所示(图左是原始论文图,其实不太好理解,可以看做了注释的图右,更好理解些),FFN 的第一层是个 MLP 宽隐层,这是 Key 层;第二层是 MLP 窄隐层,是 Value 层。FFN 的输入层其实是某个单词对应的 MHA 的输出结果Embedding,也就是通过 Self Attention,将整个句子有关的输入上下文集成到一起的 Embedding,代表了整个输入句子的整体信息。</p>\n\n<p>Key 层的每个神经元节点,记载了一对信息。比如对于上图中 FFN 第一个隐层的第 i 个节点 ki,也许就是它记载了 <北京,is-capital-of,中国> 这条知识。ki 节点对应的 key 向量,其实指的是节点 ki 和输入层每个节点的权重向量;而对应的 Value 向量,指的是节点 ki 和 FFN 第二层的 Value 层每个节点形成连接的权重向量。每个神经元的 Key 向量,用于识别输入中的某种语言或者知识模式,是一种模式探测器。如果输入中包含它要检测的某种模式,那么输入向量和 ki 节点的 key 权重进行向量内积计算,加上 Relu,形成 ki 的大数值响应,意味着 ki 检测到了这个模式,于是再把这个响应值,通过 ki 节点的 Value 权重向量向 FFN 第二层传播。这等价于将 Value 向量的值,用响应值加权,然后传递并体现到第二层 Value 层每个节点的输出上。如此这般,FFN 的正向传播计算过程,看起来就像是通过 Key 检测到某种知识模式,然后取出对应的 Value,并把 Value 体现在FFN的第二层输出上。当然,FFN 第二层每个节点,会收集 FFN 的 Key 层所有节点信息,所以是一种混合响应,而 Value 层所有节点的混合响应,可以解读为代表输出单词的概率分布信息。</p>\n\n<p>听着可能还是比较复杂,我们用个极端的例子来说明。我们假设上图的节点 ki就是记载 <北京,is-capital-of,中国>这条知识的 Key-Value 存储器,它的 Key 向量,用于检测「中国的首都是…」这个知识模式,它的 Value 向量,基本存储了与单词「北京」的 Embedding 比较接近的向量。当 Transformer 的输入是「中国的首都是[Mask]」的时候,ki 节点从输入层探测到这个知识模式,所以产生较大的响应输出。我们假设 Key 层其它神经元对这个输入都没有任何响应,那么对应的Value层的节点,其实只会接收到「北京」这个 Value 对应的单词 embedding,并通过 ki的大响应值,进行了进一步的数值放大。于是,Mask 位置对应的输出,就自然会输出「北京」这个单词。基本就是这么个过程,看着很复杂,其实很简单。</p>\n\n<p>而且这篇文章还指出,Transformer 低层对句子的表层模式作出反应,高层对语义模式作出反应,就是说低层FFN存储词法、句法等表层知识,中层和高层存储语义及事实概念知识,这和其它研究结论是一致的。</p>\n\n<p><strong>要我猜,把 FFN 看成 Key-Value 存储器这种思路,很可能不是最终的正确答案,但是距离最终正确答案的距离,估计也不太远</strong>。</p>\n\n<h3 id=\"3知识涂改液如何修正-llm-里存储的知识\">3、知识涂改液:如何修正 LLM 里存储的知识</h3>\n\n<p>既然我们已知具体的某条世界知识存储在某个或者某些 FFN 节点的参数里,自然会引发另外一个问题:我们能否修正 LLM 模型里存储的错误或者过时的知识呢?比如对于问题「英国的现任首相是谁?」鉴于近年来英国首相频繁更迭,你猜 LLM 更倾向输出「鲍里斯」还是更青睐「苏纳克」?很明显训练数据中包含“鲍里斯”的数据会更多,这种情况很大可能 LLM 会给出错误回答,于是我们就有修正 LLM 里存储的过时知识的必要性。</p>\n\n<p>如果归纳下,目前有三类不同方法来修正 LLM 里蕴含的知识:</p>\n\n<p>第一类方法从训练数据的源头来修正知识。<a href=\"https://arxiv.org/pdf/2205.11482.pdf\">《Towards Tracing Factual Knowledge in Language Models Back to the Training Data》</a>这篇文章的研究目标是:对于指定的某条知识,我们是否可以定位到是哪些训练数据导致 LLM 学会了这条知识?答案是肯定的,这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术,假设我们想要删除某条知识,则可首先定位到其对应的数据源头,删除数据源,然后重新预训练整个 LLM 模型,这样即可达成删除 LLM 中相关知识的目的。但是这里有个问题,如果修正一小部分知识,我们就需要重新做一次模型预训练,这样做明显成本太高。所以这种方法不会太有发展前景,可能比较适合那种对于某个特定类别数据的一次性大规模删除场合,不适合少量多次的常规知识修正场景,比如可能比较适合用来做去除偏见等去 toxic 内容的处理。</p>\n\n<p>第二类方法是对 LLM 模型做一次 fine-tuning 来修正知识。一个直观能想到的方法是:我们可以根据要修正成的新知识来构建训练数据,然后让 LLM 模型在这个训练数据上做 fine-tuning,这样指导 LLM 记住新的知识,遗忘旧的知识。这个方法简单直观,但是也有一些问题,首先它会带来灾难遗忘问题,就是说除了忘掉该忘的知识,还忘掉了不该忘的知识,导致这么做了之后有些下游任务效果下降。另外,因为目前的 LLM 模型规模非常大,即使是做 fine-tuning,如果次数频繁,其实成本也相当高。对这种方法感兴趣的可以参考<a href=\"https://arxiv.org/pdf/2012.00363.pdf\">《Modifying Memories in Transformer Models》</a>。</p>\n\n<p>另外一类方法直接修改 LLM 里某些知识对应的模型参数来修正知识。假设我们想要把旧知识 <英国,现任首相,鲍里斯>,修正到 <英国,现任首相,苏纳克>。首先我们想办法在 LLM 模型参数中,定位到存储旧知识的 FFN 节点,然后可以强行调整更改 FFN 中对应的模型参数,将旧知识替换成新的知识。可以看出,这种方法涉及到两项关键技术:首先是如何在 LLM 参数空间中定位某条知识的具体存储位置;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。理解这个修正 LLM 知识的过程,其实对于更深入理解 LLM 的内部运作机制是很有帮助的。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:如何修改已存储的知识?<br />\n首先是如何定位存哪了;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考<a href=\"https://arxiv.org/pdf/2202.05262\">《Locating and Editing Factual Associations in GPT》</a>和<a href=\"https://arxiv.org/pdf/2210.07229\">《Mass-Editing Memory in a Transformer》</a>。</p>\n</blockquote>\n\n<h2 id=\"三规模效应当-llm-越来越大时会发生什么\">三、规模效应:当 LLM 越来越大时会发生什么</h2>\n\n<p>我们知道,近年来,LLM 模型规模在快速增长,目前效果最好的 LLM 模型,其参数规模大都超过了千亿(100B)参数规模。比如,OpenAI 的 GPT 3 的规模为 175B,Google 的 LaMDA 规模为 137B,PaLM 的规模为 540B,DeepMind 的 Gogher 规模为 280B 等,不一而足。国内也有中文巨型模型,比如智源 GLM 规模 130B,华为「盘古」规模 200B,百度「文心」规模 260B,浪潮「源1.0」规模 245B。那么,一个很自然的问题就是:随着 LLM 模型规模不断增长,会发生些什么呢?</p>\n\n<p>预训练模型的应用往往是两阶段的:预训练阶段,及具体场景应用阶段。在预训练阶段,其优化目标是交叉熵,对 GPT 这种自回归语言模型来说,也就是看 LLM 是否正确预测到了下一个单词;而场景应用阶段,一般要看具体场景的评价指标。一般我们的直觉是:如果 LLM 模型在预训练阶段的指标越好,自然它解决下游任务的能力就越强。然而,事实并非完全如此。现有研究已证明,预训练阶段的优化指标确实和下游任务表现出正相关关系,但是并非完全正相关。也就是说,只看预训练阶段的指标,来判断一个 LLM 模型是否够好,这是不够的。基于此,我们分头来看在这两个不同阶段,随着 LLM 模型增大,有什么影响。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-6.jpeg\" alt=\"image\" /></p>\n\n<p>首先,我们先看在预训练阶段,随着模型规模逐步增大,会发生什么。OpenAI 在<a href=\"https://arxiv.org/pdf/2001.08361\">《Scaling Laws for Neural Language Models》</a>中专门研究了这个问题,并提出 LLM 模型所遵循的「伸缩法则(scaling law)」。如上图所示,这个研究证明:<strong>当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好</strong>。</p>\n\n<p>既然三个因素都重要,那么我们在实际做预训练的时候,就有一个算力如何分配的决策问题:假设用于训练 LLM 的算力总预算(比如多少 GPU 小时或者 GPU 天)给定,那么是应该多增加数据量、减少模型参数呢?还是说数据量和模型规模同时增加,减少训练步数呢?此消彼长,某个要素规模增长,就要降低其它因素的规模,以维持总算力不变,所以这里有各种可能的算力分配方案。最终 OpenAI 选择了同时增加训练数据量和模型参数,但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了:对于训练数据量和模型参数这两个要素,如果只单独增加其中某一个,这不是最好的选择,最好能按照一定比例同时增加两者,它的结论是优先增加模型参数,然后才是训练数据量。假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 5.5 倍的模型参数量,1.8 倍的训练数据量,此时模型效果最佳。</p>\n\n<p>DeepMind 的一项研究(参考<a href=\"https://arxiv.org/pdf/2203.15556\">《Training Compute-Optimal Large Language Models》</a>)更深入地探究了这个问题,其基本结论和 OpenAI 的结论差不多,比如确实需要同时增加训练数据量和模型参数,模型效果才会更好。而很多大模型在做预训练的时候,并没有考虑这一点,很多 LLM 大模型只是单调增加模型参数,而固定住了训练数据量,这个做法其实是不对的,限制了 LLM 模型的潜力。但是它修正了两者的比例关系,<strong>认为训练数据量和模型参数是同等重要的,也就是说,假设用于训练 LLM 的算力总预算增加了 10 倍,那么应该增加 3.3 倍的模型参数量,3.3 倍的训练数据量,这样模型效果才最好</strong>。</p>\n\n<p>这意味着:增加训练数据量的重要性,比我们之前所认为的,还要重要。基于这个认知,DeepMind 在设计 Chinchilla 模型时,在算力分配上选择了另外一种配置:对标数据量 300B、模型参数量 280B 的 Gopher 模型,Chinchilla 选择增加 4 倍的训练数据,但是将模型参数降低为 Gopher 的四分之一,大约为70B。但是无论预训练指标,还是很多下游任务指标,Chinchilla 效果都要优于规模更大的 Gopher。</p>\n\n<p>这带给我们如下启示:我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。缩小模型规模有很多好处,比如在应用的时候,推理速度会快很多等,无疑这是一个很有前途的 LLM 发展路线。</p>\n\n<p>以上是从预训练阶段来看模型规模的影响,如果从 LLM 解决下游具体任务效果的角度来看,随着模型规模增大,不同类型的任务有不同的表现,具体而言,有以下三类情况。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-7.jpeg\" alt=\"image\" /></p>\n\n<p>第一类任务完美体现了 LLM 模型的 scaling law,就是说随着模型规模逐步放大,任务的表现越来越好,如上图里的(a)图所示。这类任务通常符合如下共性:它们往往都是知识密集型任务,也就是说如果 LLM 模型包含的知识量越多,这类任务表现越好。而很多研究已经证明越大的 LLM 模型学习效率越高,也就是说相同训练数据量,模型越大任务效果越好,说明面对的即使是同样的一批训练数据,更大的 LLM 模型相对规模小一些的模型,从中学到了更多的知识。更何况一般情况下,在增大 LLM 模型参数的时候,往往会同步增加训练数据量,这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图,为何随着模型规模增大,这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务,其实都属于这种知识密集型任务,而很多任务在近两年获得了极大的效果提升,甚至超过了人类表现。很明显,这大概率是 LLM 模型的规模增长带来的,而非归功于某项具体的技术改进。</p>\n\n<p>第二类任务展现出 LLM 具备某种「<strong>涌现能力(Emergent Ability)</strong>」,如上图(b)所示。所谓「涌现能力」,指的是当模型参数规模未能达到某个阀值时,模型基本不具备解决此类任务的任何能力,体现为其性能和随机选择答案效果相当,但是当模型规模跨过阀值,LLM 模型对此类任务的效果就出现突然的性能增长。也就是说,模型规模是解锁(unlock)LLM 新能力的关键,随着模型规模越来越大,会逐渐解锁 LLM 越来越多的新能力。这是个很神奇的现象,因为它意味着如下让人对未来可报乐观预期的可能:或许很多任务,目前 LLM 还不能很好地解决,甚至站在现在这个时刻的我们看起来,LLM 完全没有能力解决这类任务,但因 LLM 具备「涌现能力」,所以如果我们继续推大模型,也许某一天它的这项能力就被突然解锁了。LLM 模型的规模增长会给我们带来意想不到的精彩礼物。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2206.04615\">《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》</a>这篇文章指出,这类<strong>体现出「涌现能力」的任务也有一些共性:这些任务一般由多步骤构成,要解决这些任务,往往需要先解决多个中间步骤,而逻辑推理能力在最终解决这类任务中发挥重要作用</strong>。思维链(Chain of Thought)Prompting 是典型的增强 LLM 推理能力的技术,能大幅提升此类任务的效果,关于 CoT 技术,在随后小节内容会做解释,此处暂不展开。</p>\n\n<p>问题是,为何 LLM 会出现这种「涌现能力」现象呢?上述文章以及<a href=\"https://arxiv.org/pdf/2206.07682\">《Emergent Abilities of Large Language Models》</a>给出了几个可能的解释:</p>\n\n<p>一种可能解释是<strong>有些任务的评价指标不够平滑</strong>。比如说有些生成任务的判断标准,它要求模型输出的字符串,要和标准答案完全匹配才算对,否则就是 0 分。所以,即使随着模型增大,其效果在逐步变好,体现为输出了更多的正确字符片段,但是因为没有完全对,只要有任何小错误都给 0 分,只有当模型足够大,输出片段全部正确才能得分。也就是说,因为指标不够平滑,所以不能体现 LLM 其实正在逐步改善任务效果这一现实,看起来就是「涌现能力」这种外在表现。</p>\n\n<p>另外一种可能的解释是:有些任务由若干中间步骤构成,随着模型规模增大,解决每个步骤的能力也在逐步增强,但是只要有一个中间步骤是错的,最终答案就是错的,于是也会导致这种表面的「涌现能力」现象。</p>\n\n<p>当然,<strong>上面的解释目前还都是猜想,至于为何 LLM 会出现这种现象,还需要进一步更深入的研究</strong>。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-8.jpeg\" alt=\"image\" /></p>\n\n<p>还有少部分任务,随着模型规模增长,任务的效果曲线展现出 U 形特性:随着模型规模逐渐变大,任务效果逐渐变差,但是当模型规模进一步增长,则效果开始越来越好,呈现出 U 形增长趋势,如上图所示的粉红色 PaLM 模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢?<a href=\"https://arxiv.org/pdf/2211.02011\">《Inverse scaling can become U-shaped》</a>这篇文章给出了一种解释:这些任务,内部其实隐含了两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。当模型规模小的时候,无法识别任意一种子任务,所以模型的表现跟随机选择答案差不多,当模型增长到中等规模的时候,主要执行的是干扰任务,所以对真正的任务效果有负面影响,体现为真正任务效果的下降,而当进一步增加模型规模,则 LLM 可以忽略干扰任务,执行真正的任务,体现为效果开始增长。</p>\n\n<p>对于那些随着模型规模增大,效果一直下降的任务,如果采用思维链(CoT)Prompting,则部分任务的表现转换为遵循 Scaling law,即模型规模越大效果越好,而其它任务则转换为U性增长曲线。这其实侧面说明了:此类任务应属于推理类型的任务,所以加入 CoT 后任务表现会发生质的变化。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:训练数据规模、模型参数规模和训练时长(步数),与最终 LLM 性能(loss 衡量)之间什么关系?<br />\n1、当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从 1 个 Epoch 到 2 个 Epoch),预训练模型在测试集上的 Loss 都会单调降低,也就是说模型效果越来越好。<br />\n2、训练数据量和模型参数是同等重要的。<br />\n3、我们可以选择放大训练数据,并同比例地减少 LLM 模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么有些模型不遵循 scaling law?三类任务:<br />\n第一类完美遵循 scaling low。<br />\n第二类过了阈值后涌现。《Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models》和《Emergent Abilities of Large Language Models》认为是指标不平滑 or 中间步骤是错的。\n第三类 U 型,《Inverse scaling can become U-shaped》猜测可能两种不同类型的子任务,一种是真正的任务,另外一种是「干扰任务(distractor task)」。</p>\n</blockquote>\n\n<h2 id=\"四人机接口从-in-context-learning-到-instruct-理解\">四、人机接口:从 In Context Learning 到 Instruct 理解</h2>\n\n<p>一般我们经常提到的人和 LLM 的接口技术包括:zero shot prompting、few shot prompting、In Context Learning,以及 Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献,会发现叫法比较乱。</p>\n\n<p>其中 Instruct 是 ChatGPT 的接口方式,就是说人以自然语言给出任务的描述,比如「把这个句子从中文翻译成英文」,类似这种。zero shot prompting 我理解其实就是现在的 Instruct 的早期叫法,以前大家习惯叫 zero shot,现在很多改成叫 Instruct。尽管是一个内涵,但是具体做法是两种做法。早期大家做 zero shot prompting,实际上就是不知道怎么表达一个任务才好,于是就换不同的单词或者句子,反复在尝试好的任务表达方式,这种做法目前已经被证明是在拟合训练数据的分布,其实没啥意思。目前 Instruct 的做法则是给定命令表述语句,试图让 LLM 理解它。所以尽管表面都是任务的表述,但是思路是不同的。</p>\n\n<p>而In Context Learning 和 few shot prompting 意思类似,就是给 LLM 几个示例作为范本,然后让LLM解决新问题。我个人认为 In Context Learning 也可以理解为某项任务的描述,只是 Instruct 是一种抽象的描述方式,In Context Learning 是一种例子示范的例子说明法。当然,鉴于目前这几个叫法用的有点乱,所以上述理解仅代表个人看法。</p>\n\n<p>所以我们此处只对 In Context Learning 和 Instruct 进行介绍,不再提 zero shot 和 few shot 了。</p>\n\n<h3 id=\"1神秘的-in-context-learning\">1、神秘的 In Context Learning</h3>\n\n<p>如果你细想,会发现 In Context Learning 是个很神奇的技术。它神奇在哪里呢?神奇在你提供给 LLM 几个样本示例,….,然后给它 x(n+1),LLM 竟然能够成功预测对应的 y(n+1)。听到这你会反问:这有什么神奇的呢?Fine-tuning 不就是这样工作的吗?你要这么问的话,说明你对这个问题想得还不够深入。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-9.jpeg\" alt=\"image\" /></p>\n\n<p>Fine-tuning 和 In Context Learning 表面看似都提供了一些例子给 LLM,但两者有质的不同(参考上图示意):Fine-tuning 拿这些例子当作训练数据,利用反向传播去修正 LLM 的模型参数,而修正模型参数这个动作,确实体现了 LLM 从这些例子学习的过程。但是,In Context Learning 只是拿出例子让 LLM 看了一眼,并没有根据例子,用反向传播去修正 LLM 模型参数的动作,就要求它去预测新例子。既然没有修正模型参数,这意味着貌似 LLM 并未经历一个学习过程,如果没有经历学习过程,那它为何能够做到仅看一眼,就能预测对新例子呢?这正是 In Context Learning 的神奇之处。这是否让你想起了一句歌词「只是因为在人群中多看了你一眼 再也没能忘掉你容颜」,而这首歌名叫「传奇」。你说传奇不传奇?</p>\n\n<p>看似 In Context Learning 没从例子里学习知识,实际上,难道 LLM 通过一种奇怪的方式去学习?还是说,它确实也没学啥?关于这个问题的答案,目前仍是未解之谜。现有一些研究各有各的说法,五花八门,很难判断哪个讲述的是事实的真相,甚至有些研究结论还相互矛盾。这里提供几个目前的说法,至于谁对谁错,只能你自己把握了。当然,我认为追求这个神奇现象背后的真相,是一个好的研究课题。</p>\n\n<p>试图证明 In Context Learning 没有从例子中学习的工作是<a href=\"https://arxiv.org/pdf/2202.12837\">《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》</a>。它发现了:在提供给 LLM 的样本示例中,yi 是否 xi 对应的正确答案,其实并不重要,如果我们把正确答案 yi 替换成随机的另外一个答案 yj ,这并不影响 In Context Learning 的效果。这起码说明了一点:In Context Learning 并没有提供给 LLM 那个从 x 映射到 y 的映射函数信息:y=f(x),否则的话你乱换正确标签,肯定会扰乱这个 y=f(x) 映射函数。也就是说,In Context Learning 并未学习这个输入空间到输出空间的映射过程。</p>\n\n<p>真正对 In Context Learning 影响比较大的是:x 和 y 的分布,也就是输入文本 x 的分布和候选答案 y 有哪些,如果你改变这两个分布,比如把 y 替换成候选答案之外的内容,则 In Context Learning 效果急剧下降。</p>\n\n<p>总之,这个工作证明了 In Context Learning 并未学习映射函数,但是输入和输出的分布很重要,这两个不能乱改。</p>\n\n<p>有些工作认为 LLM 还是从给出的示例学习了这个映射函数 y=f(x),不过是种隐式地学习。比如<a href=\"https://arxiv.org/pdf/2211.15661.pdf\">《What learning algorithm is in-context learning? Investigations with linear models》</a>认为 Transformer 能够隐式地从示例中学习 x 到 y 的映射过程,它的激活函数中包含了一些简单映射函数,而 LLM 通过示例能够激发对应的那一个。而<a href=\"https://arxiv.org/pdf/2212.10559\">《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》</a>这篇文章则将 ICL 看作是一种隐式的 Fine-tuning。</p>\n\n<p>总而言之,<strong>目前这还是一个未解之谜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】LLM 技术增量重点</strong>:In Context Learning</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:为什么 In Context Learning 有效?<br />\n1、《Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?》认为 ICL 没有从例子里学习 x 到 y 的映射关系,而只是学习了分布与分布的对应。<br />\n2、《What learning algorithm is in-context learning? Investigations with linear models》认为 ICL 隐式地学习了 x 到 y 的映射关系。<br />\n3、《Why Can GPT Learn In-Context? Language Models Secretly Perform Gradient Descent as Meta-Optimizers》认为 ICL 是隐式的 fine-tuning。</p>\n</blockquote>\n\n<h3 id=\"2神奇的-instruct-理解\">2、神奇的 Instruct 理解</h3>\n\n<p>我们可以把 Instruct 当作一种方便人类理解的任务表述,在这个前提下,目前关于 Instruct 的研究可以分成两种:偏学术研究的 Instruct,以及关于人类真实需求描述的 Instruct。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-10.jpeg\" alt=\"image\" /></p>\n\n<p>我们先来看第一种:偏学术研究的 Instruct。它的核心研究主题是多任务场景下,LLM 模型对 Instruct 理解的泛化能力。如上图中 FLAN 模型所示,就是说有很多 NLP 任务,对于每个任务,研究人员构造一个或者多个 Prompt 模版作为任务的 Instruct,然后用训练例子对 LLM 模型进行微调,让 LLM 以同时学习多个任务。训练好模型后,给 LLM 模型一个它没见过的全新任务的 Instruct,然后让 LLM 解决 zero shot 任务,从任务解决得是否足够好,来判断 LLM 模型是否有对 Instruct 理解的泛化能力。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】FLAN 是什么?</strong>2021 年 9 月 Google Research 团队在文章<a href=\"https://arxiv.org/pdf/2109.01652\">《Finetuned Language Models Are Zero-Shot Learners》</a>中提出的「Finetuned Language 」<br />\n1、FLAN 是 Finetuned LAnguage Net 的缩写,它用 Multi-task Learning 的方法和一种别出心裁的微调方式对 PLM 进行微调,在参数少 400 亿的情况下,性能超越 GPT-3。<br />\n2、部分参考自:https://juejin.cn/post/7064919723498012703 <br />\n3、FLAN 训练:对多个任务,把 Prompt 模板写成 Instruct,以此微调来学习。<br />\n4、FLAN 测试:新类型的任务,直接 zero-shot,判断 LLM 对 Instruct 理解的泛化能力。</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】如何增加 LLM 模型 Instruct 泛化能力</strong> <br />\n1、增加训练任务的数量 <br />\n2、增加训练任务的类型多样性 <br />\n3、增加 LLM 模型参数规模 <br />\n4、提供 CoT Prompting</p>\n</blockquote>\n\n<p>如果归纳下目前的研究结论(可参考<a href=\"https://arxiv.org/pdf/2210.11416\">《Scaling Instruction-Finetuned Language Models》</a>/<a href=\"https://arxiv.org/pdf/2204.07705\">《Super-Natural Instructions: Generalization via Declarative Instructions on 1600+ NLP Tasks》</a>),能够有效增加 LLM 模型 Instruct 泛化能力的因素包括:增加多任务的任务数量、增加 LLM 模型大小、提供 CoT Prompting, 以及增加任务的多样性。如果采取任意一项措施,都可以增加 LLM 模型的 Instruct 理解能力。</p>\n\n<p>第二种是人类真实需求下的 Instruct,这类研究以 InstructGPT 和 ChatGPT 为代表。这类工作也是基于多任务的,但是和偏向学术研究类工作最大的不同,在于它是面向人类用户真实需求的。为什么这么说呢?因为它们用于 LLM 多任务训练的任务描述 Prompt,是从大量用户提交的真实请求中抽样而来的,而不是固定好研究任务的范围,然后让研究人员来写任务描述 prompt。这里所谓的「真实需求」,体现在两个方面:首先,因为是从用户提交的任务描述里随机抽取的,所以涵盖的任务类型更多样化,也更符合用户的真实需求;其次,某个任务的 prompt 描述,是用户提交的,体现了一般用户在表达任务需求时会怎么说,而不是你认为用户会怎么说。很明显,这类工作改出来的 LLM 模型,用户体验会更好。</p>\n\n<p><a href=\"https://arxiv.org/pdf/2203.02155.pdf\">InstructGPT 论文</a>里,也拿这种方法和 FLAN 那种 Instruct based 方法做了比较。首先在 GPT3 上用 FLAN 提到的任务、数据以及 Prompt 模版进行微调,来在 GPT 3 上复现 FLAN 方法,然后和 InstructGPT 进行比较,因为 InstructGPT 的基础模型也是 GPT3,所以只有数据和方法的差别,两者可比,结果发现 FLAN 方法的效果,距离 InstructGPT 有很大的差距。那么背后的原因是什么呢?论文分析数据后认为,<strong>FLAN 方法涉及到的任务领域相对少</strong>,是 InstructGPT 涉及领域的子集,所以效果不好。也就是说,<strong>FLAN 论文里涉及到的任务和用户真实需求是不符的</strong>,而这导致在真实场景下效果不够好。而这对我们的启示是:从用户数据中收集真实需求,这事情是很重要的。</p>\n\n<blockquote>\n <p><strong>LLM 技术增量重点</strong>:Instruct <br />\n1、FLAN 的任务领域太少。<br />\n2、FLAN 不是从用户数据收集真实需求(研究人员构造任务),与用户真实需求不符。</p>\n</blockquote>\n\n<h3 id=\"3in-context-learning-和-instruct-的联系\">3、In Context Learning 和 Instruct 的联系</h3>\n\n<p>如果我们假设 In Context Learning 是用一些例子来具象地表达任务命令,Instruct 是一种更符合人类习惯的抽象任务描述。那么,一个很自然的问题是:它们之间有什么联系吗?比如,我们是否能够提供给 LLM 完成某个任务的若干具体示例,让 LLM 找出其对应的自然语言描述的 Instruct 命令?</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-11.jpeg\" alt=\"image\" /></p>\n\n<p>目前有零星的工作在探索这个问题,我认为这个方向是很有研究价值的。先说答案,答案是:Yes,LLM Can。<a href=\"https://arxiv.org/pdf/2211.01910\">《Large Language Models Are Human-Level Prompt Engineers》</a>是做这个方向很有趣的工作,如上图所示,对于某项任务,给 LLM 一些示例,让 LLM 自动生成能够描述这项任务的自然语言命令,然后它再用 LLM 生成的任务描述去测试任务效果。它使用的基础模型是 GPT 3 和 InstructGPT,经过这项技术加持后,LLM 生成的 Instruct 的效果相比未采用这项技术的 GPT 3 以及 InstuctGPT 来说,指标有极大地提升,而且在一些任务上超过人类的表现。</p>\n\n<p>这说明了:<strong>具象的任务示例和任务的自然语言描述之间,有种神秘的内在联系。至于这种联系到底是什么?我们目前对此还一无所知</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:Instruct 和 ICL 之间的联系 <br />\n1、ICL 是给了一些具象例子的命令 <br />\n2、Instruct 相当于是抽象命令</p>\n</blockquote>\n\n<h2 id=\"五智慧之光如何增强-llm-的推理能力\">五、智慧之光:如何增强 LLM 的推理能力</h2>\n\n<p>目前很多研究已证明 LLM 对于知识具有强大的记忆能力,但是,一般我们不会因为一个人记忆能力强,就说这人很聪明,是否具有强大的推理能力,往往是我们判断一个人是否聪明的重要标准。类似的,如果 LLM 的效果想让人觉得很惊艳,强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点,去推导出新知识或新结论。关于 LLM 的推理能力,是最近一年来 LLM 里最重要和热门的研究领域之一。于是,我们关心的问题就是:<strong>LLM 具备推理能力吗?如果具备,那么它的推理能力够强吗?</strong></p>\n\n<blockquote>\n <p><strong>【麦克船长注释】2023 年初对于 LLM 的疑问</strong>:LLM 具备推理能力吗?</p>\n</blockquote>\n\n<p>这两个问题目前的答案似乎应该是:当模型规模足够大的时候,LLM 本身是具备推理能力的,在简单推理问题上,LLM 已经达到了很好的能力,但是复杂推理问题上,还需要更多深入的研究。</p>\n\n<p>如果梳理现有 LLM 推理相关工作的话,我把它们归到两大类,体现出挖掘或促进 LLM 推理能力不同的技术思路:第一类研究比较多,可以统称为<strong>基于 Prompt 的方法</strong>,核心思想是通过合适的提示语或提示样本,更好地激发出 LLM 本身就具备的推理能力,Google 在这个方向做了大量很有成效的工作。第二类做法是在<strong>预训练过程中引入程序代码</strong>,和文本一起参与预训练,以此进一步增强 LLM 的推理能力,这应该是 OpenAI 实践出的思路。比如 ChatGPT 肯定具备很强的推理能力,但它并不要求用户必须提供一些推理示例,所以 <strong>ChatGPT 强大的推理能力,大概率来源于使用代码参与 GPT 3.5 的预训练</strong>。</p>\n\n<p>这两种思路其实大方向是迥异的:利用代码增强 LLM 推理能力,这体现出一种通过增加多样性的训练数据,来直接增强 LLM 推理能力的思路;而基于 Prompt 的方法,它并不会促进 LLM 本身的推理能力,只是让 LLM 在解决问题过程中更好地展示出这种能力的技术方法。可以看出,前者(代码方法)治本,后者治标。当然,两者其实也是互补的,但从长远看,治本的方法更重要。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】挖掘或促进 LLM 推理能力的两个技术思路</strong>:<br />\n1、Google 有大量研究成果的基于 Prompt 的方法:对应 ICL,挖掘 LLM 的推理能力 —— 基于神奇的 ICL <br />\n2、OpenAI 实践出真知的策略 —— Pre-training 时引入程序代码</p>\n</blockquote>\n\n<h3 id=\"1基于-prompt-的方法\">1、基于 Prompt 的方法</h3>\n\n<p>这方面工作非常多,如果归纳一下的话,大致可以分为三条技术路线。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-12.jpeg\" alt=\"image\" /></p>\n\n<p>第一种思路是直接在问题上追加辅助推理 Prompt。这种方法简单直接,但在众多领域都很有效。这个做法是由<a href=\"https://arxiv.org/pdf/2205.11916\">《Large language models are zero-shot reasoners》</a>提出的,也被称为 zero-shot CoT。具体而言,分为两个阶段(如上图所示),第一阶段在提问的问题上追加「Let’s think step by step」这句提示语,LLM 会输出具体的推理过程;第二阶段,在第一阶段的问题后,拼接 LLM 输出的具体推理过程,并再追加 Prompt=“Therefore, the answer (arabic numerals) is”,此时 LLM 会给出答案。如此简单的操作,却可以大幅增加 LLM 在各项推理任务中的效果,比如在数学推理测试集 GSM8K 上,加上提示语后,推理准确率直接从原先的 10.4% 提升到了 40.4%,可谓神奇。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>加上「Let’s think step by step」立马就提升了 GSM8K 数学推理测试集的准确率 +30pt!</p>\n</blockquote>\n\n<p>为什么 LLM 会具备给一句「Let’s think step by step」提示语,就能列出详细的推理步骤并算出答案呢?其原因目前尚无定论,我的猜测是:<strong>很可能因为预训练数据里面存在大量的此种数据,就是以「Let’s think step by step」开头,然后后面是详细的推理步骤,最后给出答案,而 LLM 在预训练的时候记住了这些模式。而当我们输入这个提示语的时候,激发 LLM 模糊得「回忆」起某些例子的推导步骤,于是即可模仿这些例子进行步骤推理并给出答案</strong>。当然这只是我的无依据推论,若事实真的如此,如果你看过后面介绍的标准 CoT 做法,会发现 Zero-shot CoT 本质上和标准 CoT 很可能没什么区别,只是标准 CoT 由人工来写推理步骤的示例,而 Zero-shot CoT 大概率是通过提示语,激活了记忆中的某些包含推理步骤的示例,很可能是如此区别。而标准 CoT 效果比 Zero-Shot CoT 效果好也完全可以理解,因为毕竟靠 LLM 回忆示例,精准性估计不会太高,而人工给出的示例,准确性是有保障的,所以自然标准 CoT 效果会更好。</p>\n\n<p><strong>这侧面说明了一个道理,就是 LLM 本身是具备推理能力的</strong>,只是我们没有办法把它的这种能力激发出来而已,通过合适的提示语来进行两步提示,就在一定程度上可以释放出它的这种潜力。另外,对于中文,很可能存在另外一个黄金提示语,比如「详细解题思路如下」,类似这种,因为中文语料在讲解推理步骤的时候,经常用的引导句和「让我们一步一步来思考」应该是不同的,这是明显的西方说法,而探索出这个中文黄金提示语,其实也是很有必要的。</p>\n\n<p>第二种思路一般被称为基于示例的思维链(few-shot CoT, Chain of Thought)Prompting。这个方向目前是 LLM 推理研究的主方向,很多工作都是在这个思路上做的,我们简单介绍几个效果显著的代表性工作,基本能代表 CoT 的技术发展方向。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-13.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 的主体思想其实很直白;为了教会 LLM 模型学会推理,给出一些人工写好的推理示例,示例里把得到最终答案前,一步步的具体推理步骤说清楚,而这些人工写的详细推理过程,就是思维链 Prompting,具体例子可参照上图中蓝色文字部分。CoT 的意思是让 LLM 模型明白一个道理;<strong>就是在推理过程中,步子不要迈得太大,否则很容易出错,改变思维模式,化大问题为小问题,步步为营,积小胜为大胜</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>这个过程已经从 programming computer 过渡成 teaching computer 了,训练 AI 越来越像一个需要人教育培养的孩子。<br />\nCoT 其实就是给一些思维链 step by step 的 prompting</p>\n</blockquote>\n\n<p>最早明确提出 CoT 这个概念的文章是<a href=\"https://arxiv.org/pdf/2201.11903\">《Chain of thought prompting elicits reasoning in large language models》</a>,论文发布于 22 年 1 月份,虽然做法很简单,但是应用 CoT 后 LLM 模型的推理能力得到了巨大提升,GSM8K 数学推理测试集准确率提高到 60.1% 左右。当然,这种给出详细推理步骤和中间过程的思想,并非 CoT 最早提出的,更早一些的「scratchpad」技术(可参考<a href=\"https://arxiv.org/pdf/2112.00114\">《Show Your Work: Scratchpads for Intermediate Computation with Language Models》</a>)首先采用了类似的思路。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-14.jpeg\" alt=\"image\" /></p>\n\n<p>CoT 提出不久,很快在 22 年 3 月份,一项被称为「Self-Consistency」的改进技术就将 GSM8K 测试集准确率提高到 74.4%,提出这项改进的论文是<a href=\"https://arxiv.org/pdf/2203.11171\">《Self-Consistency Improves Chain of Thought Reasoning in Language Models》</a>。「Self-Consistency」的思路也很直观(参考上图):首先可以利用 CoT 给出几个写了推理过程的示例,然后要求 LLM 对给定的问题进行推理,如果是 CoT,直接输出一个推理过程和答案,整个过程就结束了。「Self-Consistency」则不然,它要求 LLM 输出多个不同的推理过程和答案,然后采用投票的方式选出最佳答案,思路非常简单直接,但是效果也确实好。「Self-Consistency」其实是教导 LLM 学会这么一个道理:孔乙己说过茴香豆的「茴」字有四种写法,类似的,一个数学题的正确解法也可以有很多种,每个不同的推导过程都指向最终的答案。条条大路通罗马,虽说也有个别迷路走到北京的,但是迷路的毕竟是少数,看看大多数人走到哪里,哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义,是不是这道理?</p>\n\n<p>再往后,<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>这个工作在「Self-Consistency」基础上,进一步集成了「从一个 Prompt 问题拓展到多个 Prompt 问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票」这三个改进点,将 GSM8K 测试集准确率提高到 83% 左右。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>GSM8K 数学推理测试集 <br />\n1、提高到 40.4%:加一句「Let’s think step by step」 <br />\n2、提高到 60.1%:应用 CoT 后,即训练时给 LLM 几个写了推理过程的示例 <br />\n3、提高到 74.7%:基于 CoT 的改进技术 Self-Consistency,给出多个不同推理过程和答案,投票选出最好答案 <br />\n4、提高到 83% 左右:基于 Self-Constistenty 的改进技术,1)一个 Prompt 拓展为多个 Prompt;2)检查推理中间步骤正确性;3)对多个输出的回答加权投票。</p>\n</blockquote>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-15.jpeg\" alt=\"image\" /></p>\n\n<p>第三种思路体现了一种分治算法的思想。当然这个所谓「分治」是我归纳的,别人没这么说。这种思路的核心思想是:对于一个复杂的推理问题,我们把它分解成若干容易解决的子问题,一一解决掉子问题后,我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得,这种思路可能才是揭示问题本质、最终解决 LLM 复杂推理问题正宗的道路。我们以「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术为例来说明这种思路的一种具体实现方式,如上图所示:它分为两个阶段,第一个阶段,从原始问题我们可以得知最终要问的问题是什么,我们假设最终问题是 Final Q,然后从原始问题填充 Prompt 模版「如果要解决 Final Q 问题,那么我需要先解决」,然后把原始问题和这个 Prompt 交给 LLM,让 LLM 模型给出答案,等于让 LLM 给出最终问题的前置子问题 Sub Q;接下来我们进入第二个阶段,让 LLM 先回答刚才拿到的子问题Sub Q,并拿到对应的答案,然后原始问题拼接子问题 Sub Q 及对应答案,再去问 LLM 最终那个问题 Final Q,此时LLM会给出最后的答案。如此这般,体现出拆解子问题,并从子问题的答案逐步找出最终答案的思路。</p>\n\n<h3 id=\"2代码预训练增强-llm-推理能力\">2、代码预训练增强 LLM 推理能力</h3>\n\n<p>以上是目前利用 Prompt 激发 LLM 模型推理能力的三种主流做法,而关于 LLM 的推理能力,目前还观察到一个有趣且费解的现象:除了文本外,如果能够加入程序代码一起参与模型预训练,则能大幅提升 LLM 模型的推理能力。这个结论从不少论文的实验部分都可以得出(可以参考<a href=\"https://arxiv.org/pdf/2210.03493\">《AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS》</a>/<a href=\"https://arxiv.org/pdf/2210.09261\">《Challenging BIG-Bench tasks and whether chain-of-thought can solve them》</a>等论文的实验部分)。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-16.jpeg\" alt=\"image\" /></p>\n\n<p>上图给出了一份实验数据,来自于论文<a href=\"https://arxiv.org/pdf/2206.02336\">《On the Advance of Making Language Models Better Reasoners》</a>,其中 GPT3 davinci 就是标准的 GPT 3 模型,基于纯文本训练;code-davinci-002(OpenAI 内部称为 Codex)是同时在 Code 和 NLP 数据上训练的模型。如果比较两者效果,可以看出,不论采用具体哪种推理方法,仅仅是从纯文本预训练模型切换到文本和 Code 混合预训练模型,在几乎所有测试数据集合上,模型推理能力都得到了巨大的效果提升,比如我们以「Self Consistency」方法为例,在大多数据集合上的性能提升,都直接超过了 20 到 50 个百分点,这是很恐怖的性能提升,而其实在具体推理模型层面,我们什么也没做,仅仅是预训练的时候除了文本,额外加入了程序代码而已。</p>\n\n<p>除了这个现象,从上图数据中,我们还可以得出其它一些结论,比如 GPT 3 这种纯文本预训练模型,其实是具备相当程度的推理能力的,除了在 GSM8K 这种数学推理上效果比较差外,其它推理数据数据集合表现也还可以,前提你需要采用合适的方法,来激发出它本身就具备的这种能力;再比如,text-davinci-002,也就是在 code-davinci-002 基础上加入 instruct fine-tuning 后的模型(就是加入 InstructGPT 或 ChatGPT 模型的第一步),其推理能力要弱于 Codex,但是有其它研究表明它在自然语言处理任务又要强于 Codex。而这貌似说明了,加入 instruct fine-tuning,会损害 LLM 模型的推理能力,但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的,也能启发后续进一步的思考和探索。</p>\n\n<p>那么,一个自然的疑问是:<strong>为何预训练模型可以从代码的预训练中获得额外的推理能力?确切原因目前未知,值得深入探索</strong>。我猜测可能是因为原始版本的 Codex(只使用代码训练,可参考文献:<a href=\"https://arxiv.org/pdf/2107.03374\">《Evaluating Large Language Models Trained on Code》</a>)的代码训练是从文本生成代码,而且代码中往往包含很多文本注释,本质上这类似于预训练模型做了 <文本,Code> 两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释,很明显这些数学类或逻辑推理类的数据,对于解决下游数学推理问题是有帮助的,我猜大概率原因在此。</p>\n\n<blockquote>\n <p><strong>2023 年初对于 LLM 的疑问</strong>:为什么预训练模型可以从代码预训练中获得额外的推力能力?</p>\n</blockquote>\n\n<h3 id=\"3关于-llm-推理能力的思考\">3、关于 LLM 推理能力的思考</h3>\n\n<p>上面介绍了 LLM 推理的主流技术思路和现有的一些结论,接下来谈谈我对 LLM 模型推理技术的思考,以下内容纯个人推断,没有太多证据,还请谨慎参考。我的判断是:虽然最近一年来,关于激发 LLM 的推理能力,这方面的技术进展很快,也取得了很大的技术进步,但是总体感觉是,我们可能走在正确的方向上,但是距离接触到真正的问题本质还有一段距离,对此要有更深入的思考和探索。</p>\n\n<p>首先,我比较赞同上述分治算法的主体思路,对于复杂的推理问题,我们应该把它拆解成若干简单的子问题,因为子问题对于 LLM 来说回答正确的概率就大很多,让 LLM 一一 回答子问题后,再逐步推导出最终答案。受到「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术的启发,如果进一步思考,我觉得 LLM 推理本质上很可能会是如下两种可能的其中之一:不断和 LLM 进行交互的图上推理问题,抑或是不断和LLM进行交互的程序流程图执行问题。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-17.jpeg\" alt=\"image\" /></p>\n\n<p>先说图上推理问题,如上图所示,假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构,图中的节点是子问题或者子步骤,图中的边代表了子问题之间的依赖关系,就是说只有回答好子问题 A,才能回答子问题 B,而且图中大概率存在循环结构,就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图,那么可以根据依赖关系,引导 LLM 一步一步按照图结构,回答必须首先回答的子问题,直到推导出最终答案。</p>\n\n<p><img src=\"/img/backup/2023-01-09-agi-llm-tech-18.jpeg\" alt=\"image\" /></p>\n\n<p>再说程序流程图问题,参考上图,假设我们有办法把复杂问题拆解成子问题或子步骤,并产生一个由子步骤构成的类似程序流程图的结构,在这个结构里,有些步骤会反复执行多次(循环结构),有些步骤的执行需要进行条件判断(条件分支)。总而言之,在执行每个子步骤的时候和 LLM 进行交互,得到子步骤的答案,然后按照流程不断执行,直到输出最终答案。类似这种模式。假设这个思路大致正确的话,也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力:大概率因为 <文本,代码> 的多模态预训练模型,在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁,将两者联系起来的,即由文本描述到隐含的流程图,再映射到由流程图产生具体的代码。也就是说,这种多模态预训练,可以增强 LLM 模型从文本构建出隐含的流程图并按照流程图执行的能力,也就是加强了它的推理能力。</p>\n\n<p>当然,上述思路最大的问题是,我们如何根据文本描述的问题,能够靠 LLM 模型,或者其它模型,得到图结构或者流程图结构?这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练,走隐式学习内部隐含结构的方法。而目前的 CoT 技术,如果套到上述思路来思考的话,可以这么理解:标准 CoT,其实就是靠自然语言文本来描述图结构或者程序流程图的;而「<a href=\"https://arxiv.org/pdf/2205.10625.pdf\">Least-to-most prompting</a>」技术,则是试图根据最后一个图节点,靠倒推来试图推导出其中的图结构,但是很明显,目前的方法限制了它倒推的深度,也就是说它只能推导出非常简单的图结构,这正是限制它能力的所在。</p>\n\n<h2 id=\"六未来之路llm-研究趋势及值得研究的重点方向\">六、未来之路:LLM 研究趋势及值得研究的重点方向</h2>\n\n<p>这里列出一些我个人认为比较重要的 LLM 研究领域,或值得深入探索的研究方向。</p>\n\n<h4 id=\"探索-llm-模型的规模天花板\">探索 LLM 模型的规模天花板</h4>\n\n<p>尽管继续推大 LLM 模型的规模,这事看似没有技术含量,但是其实这个事情异常重要。我个人判断,自从 Bert 出现以来,到 GPT 3,再到 ChatGPT,大概率这些给人印象深刻的关键技术突破,核心贡献都来自于 LLM 模型规模的增长,而非某项具体技术。说不定,揭开 AGI 真正的钥匙就是:超大规模及足够多样性的数据、超大规模的模型,以及充分的训练过程。再者,做超大规模的 LLM 模型,对技术团队的工程实现能力要求是非常高的,也不能认为这事情缺乏技术含量。</p>\n\n<p>那么继续推大 LLM 模型规模,有什么研究意义呢?我觉得有两方面的价值。首先,如上所述,我们已知,对于知识密集型的任务,随着模型规模越大,各种任务的效果会越来越好;而对很多推理类型的有难度的任务,加上 CoT Prompting 后,其效果也呈现出遵循 Scaling law 的趋向。那么,很自然的一个问题就是:对于这些任务,LLM 的规模效应,能将这些任务解决到何种程度?这是包括我在内,很多人关心的问题。其次,考虑到 LLM 具备的神奇的「涌现能力」,如果我们继续增加模型规模,它会解锁哪些让我们意想不到的新能力呢?这也是很有意思的问题。考虑到以上两点,我们仍然需要不断增大模型规模,看看模型规模对解决各类任务的天花板在哪里。</p>\n\n<p>当然,这种事情也就只能说说,对 99.99% 的从业者来说,是没有机会和能力做这个事情的。要做这个事情,对研究机构的财力及投入意愿、工程能力、技术热情,都有极高的要求,缺一不可。能做这事情的机构,粗估下来,国外不超过 5 家,国内不超过 3 家。当然,考虑到成本问题,未来也许会出现「股份制大模型」,就是有能力的几家机构合作,群策群力,一起来共建超级大模型的现象。</p>\n\n<h4 id=\"增强-llm-的复杂推理能力\">增强 LLM 的复杂推理能力</h4>\n\n<p>正如之前对 LLM 推理能力的叙述,尽管 LLM 在最近一年推理能力得到了很大的提升,但是很多研究(参考<a href=\"https://arxiv.org/pdf/2208.05051\">《Limitations of Language Models in Arithmetic and Symbolic Induction》</a>/<a href=\"https://arxiv.org/pdf/2206.10498\">《Large Language Models Still Can’t Plan》</a>)表明,目前 LLM 能够解决得比较好的推理问题,往往都相对简单,LLM 的复杂推理能力仍然薄弱,比如即使是简单的字符拷贝推理或者加减乘除运算,当字符串或者数字非常长的时候,LLM 推理能力会极速下降,再比如行为规划能力等复杂推理能力很弱。总而言之,加强 LLM 的复杂推理能力,应该是 LLM 未来研究中最重要的环节之一。</p>\n\n<p>前文有述,<strong>加入代码加入预训练,这是一种直接增强 LLM 推理能力的方向。这个方向目前研究尚显不足,更像是实践经验的总结</strong>,探索背后的原理,并进而引入更多类型除代码外的新型数据来增强 LLM 的推理能力,这可能是更本质提升推理能力的方向。</p>\n\n<h4 id=\"llm-纳入-nlp-之外更多其它研究领域\">LLM 纳入 NLP 之外更多其它研究领域</h4>\n\n<p>目前的 ChatGPT 擅长 NLP 和 Code 任务,作为通向 AGI 的重要种子选手,<strong>将图像、视频、音频等图像与多模态集成进入 LLM,乃至 AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入 LLM</strong>,是 LLM 通往 AGI 的必经之路。而这个方向才刚刚开始,因此具备<strong>很高的研究价值</strong>。</p>\n\n<h4 id=\"更易用的人和-llm-的交互接口\">更易用的人和 LLM 的交互接口</h4>\n\n<p>如前所述,ChatGPT 的最大技术贡献即在此。但是很明显,目前的技术并不完美,肯定还有很多命令 LLM 理解不了。所以,沿着这个方向,寻找更好的技术,<strong>来让人类使用自己习惯的命令表达方式,而 LLM 又能听懂,这是个新的,且非常有前景的技术方向</strong>。</p>\n\n<h4 id=\"建设高难度的综合任务评测数据集\">建设高难度的综合任务评测数据集</h4>\n\n<p>好的评测数据集,是引导技术不断进步的基石。随着 LLM 模型逐步增大,任务效果快速提升,导致很多标准测试集快速过时。也就是说,这些数据集合相对现有技术来说,太容易了,在没有难度的测试集合下,我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合,是促进 LLM 技术进步的关键所在。</p>\n\n<p>目前行业应出现了一些新的测试集,有代表性的包括 BIGBench、OPT-IML 等。这些测试集合体现出一些特性,比如相对 LLM 现有技术具备一定的难度、综合了各种各样多种类型的任务等。</p>\n\n<p>受到 ChatGPT 的启发,我觉得除此外应纳入另一考虑因素:体现真实用户需求。就是说,这些任务的表述由用户真实发起,这种方式构建出来的 LLM 模型,才能解决用户实际需求。</p>\n\n<p>除此外,相信 LLM 会快速将能力溢出到 NLP 之外的领域,而如何融入更多其它领域的评测数据,也是需要提前去考虑。</p>\n\n<h4 id=\"高质量数据工程\">高质量数据工程</h4>\n\n<p>对于预训练模型来说,数据是其根本,预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此,我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。</p>\n\n<p>关于数据,需要考虑两个方面:数据的质量和数量。而根据 T5 的对比实验,我们可以得出结论:在数量和质量两个因素里,质量优先,正确的道路应该是在保证数据质量的前提下,再去增大数据规模。</p>\n\n<p>数据质量,包括数据的信息含量以及数据的多样性等多个衡量标准,比如 Wiki 明显就属于世界知识密度极高的高质量数据,这是从信息含量来说的;而增加数据类型的多样性,无疑是激发 LLM 各种新能力的根本,比如加入问答网站的数据,对于 LLM 的 QA 能力提升是有直接帮助的。多样化的数据赋予了 LLM 更好解决更多不同类型任务的能力,所以,这可能是数据质量里最关键的标准。</p>\n\n<p>关于数据数量,原则上互联网上公开发布的数据都可以纳入 LLM 模型的预训练过程。那么,它的极限在哪里?<a href=\"https://arxiv.org/pdf/2211.04325\">《Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning》</a>对此进行了估算,结论是到 2026 年左右,高质量的NLP数据将会用光,低质量 NLP 数据会在 2030 到 2050 年用光,而低质量图像数据会在 2030 到 2060 年用光。而这意味着:要么到时我们有新类型的数据源,要么我们必须增加 LLM 模型对数据的利用效率。否则,目前这种数据驱动的模型优化方式将会停止进步,或者收益减少。</p>\n\n<h4 id=\"超大-llm-模型-transformer-的稀疏化\">超大 LLM 模型 Transformer 的稀疏化</h4>\n\n<p>目前规模最大的 LLM 中,有相当比例的模型采取了稀疏(Sparse)结构,比如 GPT 3、PaLM、GLaM 等,GPT 4 大概率也会走稀疏模型路线。之所以采用 Sparse 化的模型,主要好处是它可以极大减少 LLM 的训练时间和在线推理时间。Switch Transformer 论文里指出:在相同算力预算的前提下,使用稀疏化 Transformer,相对 Dense Transformer,LLM 模型的训练速度可以提升 4 倍到 7 倍。为何 Sparse 模型可以加快训练和推理时间呢?这是因为尽管模型参数巨大,但是对于某个训练实例,Sparse 模型通过路由机制,只使用整个参数中的一小部分,参与训练和推理的活跃参数量比较少,所以速度快。</p>\n\n<p>我认为未来超大的 LLM 模型大概率会收敛到稀疏模型。主要有两个原因:一方面,现有研究表明(参考<a href=\"https://arxiv.org/pdf/2210.06313\">《Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers》</a>),标准的 Dense Transformer在训练和推理时,它本身也是稀疏激活的,就是说只有部分参数会被激活,大部分参数没有参与训练和推理过程。既然这样,我们不如直接迁移到稀疏模型;另外,毫无疑问 LLM 模型的规模会继续推大,而高昂的训练成本是妨碍其进一步扩大模型的重要阻力,<strong>使用稀疏模型可以极大降低超大模型的训练成本</strong>,所以随着模型规模越大,稀疏模型带来的收益越明显。考虑到这两个方面,大概率未来更大的 LLM 模型会采用稀疏模型方案。</p>\n\n<p>那为何目前其它大规模模型不走稀疏模型的路线呢?因为 Sparse 模型存在训练不稳定、容易过拟合等问题,不太容易训练好。所以,如何修正稀疏模型面临的问题,<strong>设计出更容易训练的稀疏模型,是很重要的未来研究方向</strong>。</p>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>重要研究拓展方向 <br />\n1、任务层 —— 增加 LLM 推理能力 <br />\n2、接口层 —— 命令更自然:怎么把 ICL 逐渐过渡成 zero-shot 的 Instruct <br />\n3、任务层 —— 多模态拓展:先在 NLP 和 Code 上奔向 AGI,把 CV、音频、AI for science、自动驾驶、机器人逐渐囊括进来 <br />\n4、模型层 —— 训练更容易:稀疏矩阵问题很多(训练不稳定、容易过拟合)的解决</p>\n</blockquote>\n\n<blockquote>\n <p><strong>【麦克船长注释】</strong>为什么稀疏结构效果好?<br />\n1、计算快:如果很稠密,则对很多知识的提炼都耦合在了一起。如果比较稀疏,就类似人脑,对不同功能、不同知识等内容是分开存储的,耦合少、计算速度就快。<br />\n2、好训练:越稀疏训练成本越低\n3、缺点:训练不稳定,容易过拟合</p>\n</blockquote>\n\n<h2 id=\"七取经之路复刻-chatgpt-时要注意些什么\">七、取经之路:复刻 ChatGPT 时要注意些什么</h2>\n\n<p>如果希望能复刻类似 ChatGPT 这种效果令人惊艳的 LLM 模型,综合目前的各种研究结论,在做技术选型时需要重点权衡如下问题:</p>\n\n<p>首先,在预训练模式上,我们有三种选择:GPT 这种自回归语言模型,Bert 这种双向语言模型,以及 T5 这种混合模式(Encoder-Decoder 架构,在 Encoder 采取双向语言模型,Decoder 采取自回归语言模型,所以是一种混合结构,但其本质仍属于 Bert 模式)。我们应选择 GPT 这种自回归语言模型,其原因在本文范式转换部分有做分析。目前看,<strong>国内 LLM 在做这方面技术选型的时候,貌似很多都走了 Bert 双向语言模型或 T5 混合语言模型的技术路线,很可能方向走偏了</strong>。</p>\n\n<p>第二,<strong>强大的推理能力是让用户认可 LLM 的重要心理基础</strong>,而如果希望 LLM 能够具备强大的推理能力,根据目前经验,最好在做预训练的时候,要引入大量代码和文本一起进行 LLM 训练。至于其中的道理,在本文前面相关部分有对应分析。</p>\n\n<p>第三,如果希望模型参数规模不要那么巨大,但又希望效果仍然足够好,此时有两个技术选项可做配置:要么增强高质量数据收集、挖掘、清理等方面的工作,意思是我模型参数可以是 ChatGPT / GPT 4 的一半,但是要想达到类似的效果,那么高质量训练数据的数量就需要是 ChatGPT/GPT 4 模型的一倍(Chinchilla 的路子);另外一个可以有效减小模型规模的路线是采取文本检索(Retrieval based)模型 + LLM 的路线,这样也可以在效果相当的前提下,极大减少 LLM 模型的参数规模。这两个技术选型不互斥,反而是互补的,也即是说,可以同时采取这两个技术,在模型规模相对比较小的前提下,达到超级大模型类似的效果。</p>\n\n<p>第四,超级大模型因为模型规模大,所以训练成本过高,导致很少有机构有能力去做这件事。而且由上文分析可见,继续不断推大 LLM 模型规模是肯定会发生、也应该去做的事情。于是,如何通过技术手段降低 LLM 的训练成本就很重要。LLM 的特征抽取器 Sparse 化是有效降低模型训练及推理成本的技术选择。由此可见,随着模型越来越大,LLM 模型 Sparse 化是一个应该考虑的选项。</p>\n\n<p>第五,ChatGPT 是目前最接近理想 LLM 的技术方案,而理想中的 LLM 应该是以一个几乎无所不能的基础通用大模型作为依托,来支持各种各样的上层任务类型。目前看,支持越来越多的任务类型,主要是通过增加 LLM 预训练数据的多样性来达成的,数据多样性越好,LLM 能够支持的任务类型就越丰富。所以,<strong>应该重视通过增加数据多样性来增加 LLM 新能力的思路</strong>。</p>\n\n<p>第六,易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务,而 LLM 要能够理解这些 Instruct 的真实含义。另外,也要注意这些 Instruct 是符合人类真实需求的,就是说,要从最终用户那里收集任务表述方式,而不能靠研发人员自己的臆想或猜测。ChatGPT 给我最大的启发其实是这一点,至于是否用增强学习我倒觉得不重要,其它替代技术应该也能做类似的事情。</p>\n\n<h2 id=\"八chatgpt为什么是-openai\">八、ChatGPT:为什么是 OpenAI</h2>\n\n<p>为什么是 OpenAI 作出了 ChatGPT,而不是其它机构呢?我们在这里可以做个简单分析。</p>\n\n<p>在本文开头,我们提到了 OpenAI 看待 LLM 的理念。OpenAI 是怎么看待 LLM 的呢?回顾它不断推出的技术,可以看出,它其实从 GPT 1.0 开始,基本就坚定地把 LLM 看做是通往 AGI 的一条必由之路。具体而言,在 OpenAI 眼中,未来的 AGI 应该长这个样子:有一个任务无关的超大型 LLM,用来从海量数据中学习各种知识,这个 LLM 以生成一切的方式,来解决各种各样的实际问题,而且它应该能听懂人类的命令,以便于人类使用。其实对 LLM 发展理念的理解,在前半部分,就是「构建一个任务无关的超大型 LLM,让它从海量数据中学习各种知识」,这一点几乎是大家的共识,能体现出 OpenAI 眼光的其实是后半部分。</p>\n\n<p>OpenAI 的理念比较超前,对自我定位从一开始就定得比较高,始终坚定不移地探索上述方式是否可以实现 AGI。OpenAI 之所以能作出 ChatGPT,胜在一个是定位比较高,另一个是不受外界干扰,态度上坚定不移。</p>\n\n<p>我们可以回顾下它走的一些关键路程:GPT 1.0 走的是生成模式的自回归语言模型路线,比 Bert 出来的还早些。Bert 证明了:双向语言模型对于很多 NLP 理解类任务,效果比自回归这种单向语言模型效果更好。尽管如此,GPT 2.0 并没有因此切换到双向语言模型这条路上,仍然走文本生成的路,而且开始尝试零示例(zero shot)prompt 和少量示例(few shot)prompt。其实这时候, OpenAI 心目中的 AGI 已经开始浮出水面,逐渐显示出轮廓了。只是因为 zero shot/few shot 效果比 Bert+fine-tuning 差的比较远,所以大家都没太当回事,甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候,我估计即使是 OpenAI 自己,也不一定能确保这条路肯定能走通。</p>\n\n<p>但是,这不妨碍它继续在这条路上往后走。GPT 3.0 已经展示出了比较强大的 zero shot/few shot prompt 能力,这时候 OpenAI 心目中的 AGI 已经完全漏出水面,轮廓清晰,而且它的效果也证明了这条路,是有较大可能走得通的。GPT 3.0 是一个决定 LLM 发展方向的叉路口和分水岭,与之对应的另外一条路是「Bert+fine-tuning」模式。在这个岔路口,不同的从业者选择走上了不同的道路,后面的技术差距也是从这里开始拉开的。很遗憾地是,国内很多从业者选择继续在「Bert+fine-tuning」这条路上往后走,这也是造成今天落后局面的一个关键时间节点。再往后,就是 InstructGPT 和 ChatGPT 了,OpenAI 通过 ChatGPT 证明了一点;虽然我们距离真正的 AGI,可能还有很长的路要走,但是通过超大 LLM 走向 AGI 这条路,目前看是可行的。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>自然语言处理 AIGC 近年的发展脉络、关键论文、技术里程碑和商业应用</title>\n \t<meta name=\"description\" content=\"火出圈的 ChatGPT,背后是自然语言处理领域近几年发展的成果。本文从近几年自然语言处理的关键发展脉络,过程中关键的几篇学术论文,这几年的所有重要行业里程碑,以及目前为止业内已经诞生的应用。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>自然语言处理 AIGC 近年的发展脉络、关键论文、技术里程碑和商业应用</h2>\t\t\n\t<time datetime=\"2022-12-24T15:08:01+00:00\" class=\"by-line\">24 Dec 2022, 杭州 | 麦克船长 | 总计 9713 字</time>\n\t<div class=\"content\">\n\t\t<ul>\n <li>作者:麦克船长(钟超)</li>\n <li>微信:sinosuperman</li>\n</ul>\n\n<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一自然语言处理领域近年的发展关键节点\" id=\"markdown-toc-一自然语言处理领域近年的发展关键节点\">一、自然语言处理领域近年的发展关键节点</a> <ul>\n <li><a href=\"#1从理性主义到经验主义\" id=\"markdown-toc-1从理性主义到经验主义\">1、从理性主义到经验主义</a></li>\n <li><a href=\"#2经验主义的早期还不是深度学习\" id=\"markdown-toc-2经验主义的早期还不是深度学习\">2、经验主义的早期,还不是深度学习</a></li>\n <li><a href=\"#3撇开特征让机器囫囵吞枣地学吧\" id=\"markdown-toc-3撇开特征让机器囫囵吞枣地学吧\">3、撇开特征,让机器「囫囵吞枣」地学吧</a></li>\n <li><a href=\"#4囫囵个儿地学习省去特征工程的人工但也少不了标注的人工\" id=\"markdown-toc-4囫囵个儿地学习省去特征工程的人工但也少不了标注的人工\">4、囫囵个儿地学习,省去特征工程的人工,但也少不了标注的人工</a></li>\n <li><a href=\"#5自监督学习法让我们省去人工标注\" id=\"markdown-toc-5自监督学习法让我们省去人工标注\">5、自监督学习法,让我们省去人工标注</a></li>\n <li><a href=\"#6用原始的任务训练出来的模型能迁移去解决新任务吗\" id=\"markdown-toc-6用原始的任务训练出来的模型能迁移去解决新任务吗\">6、用原始的任务训练出来的模型,能迁移去解决新任务吗?</a></li>\n <li><a href=\"#7从理解到生成nlp-是最直面-aigc-最硬核难题的领域\" id=\"markdown-toc-7从理解到生成nlp-是最直面-aigc-最硬核难题的领域\">7、从理解到生成,NLP 是最直面 AIGC 最硬核难题的领域</a></li>\n <li><a href=\"#8数据和算力有了还不够\" id=\"markdown-toc-8数据和算力有了还不够\">8、数据和算力有了,还不够</a></li>\n </ul>\n </li>\n <li><a href=\"#二学术里程碑几篇重量级论文\" id=\"markdown-toc-二学术里程碑几篇重量级论文\">二、学术里程碑:几篇重量级论文</a> <ul>\n <li><a href=\"#0提出-attention-机制的neural-machine-translation-by-jointly-learning-to-align-and-translate2015\" id=\"markdown-toc-0提出-attention-机制的neural-machine-translation-by-jointly-learning-to-align-and-translate2015\">0、提出 Attention 机制的《Neural Machine Translation by Jointly Learning to Align and Translate》(2015)</a></li>\n <li><a href=\"#1提出-transformer-的attention-is-all-you-need2017\" id=\"markdown-toc-1提出-transformer-的attention-is-all-you-need2017\">1、提出 Transformer 的《Attention is All You Need》(2017)</a></li>\n <li><a href=\"#2elmo-deep-contextualized-word-representations\" id=\"markdown-toc-2elmo-deep-contextualized-word-representations\">2、ELMo: Deep contextualized word representations</a></li>\n <li><a href=\"#3gpt-1improving-language-understanding-by-generative-pre-training\" id=\"markdown-toc-3gpt-1improving-language-understanding-by-generative-pre-training\">3、GPT-1:Improving Language Understanding by Generative Pre-Training</a></li>\n <li><a href=\"#4bert-pre-training-of-deep-bidirectional-transformers-for-language-understanding2018\" id=\"markdown-toc-4bert-pre-training-of-deep-bidirectional-transformers-for-language-understanding2018\">4、BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding(2018)</a></li>\n <li><a href=\"#5gpt-2\" id=\"markdown-toc-5gpt-2\">5、GPT-2:</a></li>\n <li><a href=\"#6gpt-3-language-models-are-few-shot-learners2020\" id=\"markdown-toc-6gpt-3-language-models-are-few-shot-learners2020\">6、GPT-3: Language Models are Few-Shot Learners(2020)</a></li>\n <li><a href=\"#7instructgpt\" id=\"markdown-toc-7instructgpt\">7、InstructGPT</a></li>\n <li><a href=\"#其他的重量级论文\" id=\"markdown-toc-其他的重量级论文\">其他的重量级论文</a></li>\n </ul>\n </li>\n <li><a href=\"#三行业里程碑\" id=\"markdown-toc-三行业里程碑\">三、行业里程碑</a></li>\n <li><a href=\"#四成本\" id=\"markdown-toc-四成本\">四、成本</a></li>\n <li><a href=\"#五业内应用\" id=\"markdown-toc-五业内应用\">五、业内应用</a></li>\n <li><a href=\"#五行业内哪些人的言论值得我们日常重点关注\" id=\"markdown-toc-五行业内哪些人的言论值得我们日常重点关注\">五、行业内哪些人的言论值得我们日常重点关注</a></li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<h3 id=\"一自然语言处理领域近年的发展关键节点\">一、自然语言处理领域近年的发展关键节点</h3>\n\n<p><img src=\"/img/src/2022-12-17-ai-bert-1-1.jpg\" alt=\"image\" /></p>\n\n<h4 id=\"1从理性主义到经验主义\">1、从理性主义到经验主义</h4>\n\n<p>自然语言处理(Natural Language Processing,简称 NLP),一开始走的是专家路线,也就是想「白盒化」来解构对自然语言的理解,这被称为「符号主义(Symbolism)」。符号主义的背后,是人类对自己用符号系统基于逻辑来完全数字化自然语言的自信。反正这条路目前是没走出来,你要非说「这其实是自负」,暂时人工智能专家们也无可辩驳。沿着这个路径的研究一直占据人工智能主流到 20 世纪 90 年代。</p>\n\n<p>这里我们想想,自然语言处理,其实是两个过程,一个是输入,即对自然语言的理解,一个是输出,即近期有点火的概念 AIGC(Artificial Intelligence Generated Content)。我们这里说说前者,人类学习语言的过程,哪有什么符号系统,哪有什么逻辑,就是被疯狂输入,然后经过很多个月之后,一个小 baby 就学会说话了,这个过程没有「理性主义」的痕迹,只有「经验主义」的胜利。那么 AI 学人话,能这样吗?</p>\n\n<p>于是就有了所谓「联结主义(Connectionism)」:你知道人的神经元网络吧?这个是一个个神经元,相互联结组成一个网络,通过这个网络来非常「黑盒化」地学习自然语言。至于这个网络里的每一个细节,我们不甚清楚,但就是可以通过这个网络模型学会自然语言,这就是一种「经验主义」。从 20 世纪 90 年代,人工智能领域就是沿着这个方向取得了巨大进展的。要注意一点,经验主义地路径解决 NLP 问题,并不等同于神经网络,但它是目前最有效的。</p>\n\n<h4 id=\"2经验主义的早期还不是深度学习\">2、经验主义的早期,还不是深度学习</h4>\n\n<p>最初的经验主义,还是主要通过人工对特征进行「经验性地」提取,对计算机来说不要让它求甚解,直接给它喂这些梳理好的「特征」就好了。而这个需要一定的专业领域知识储备,加上人工地提取特征的操作过程,被称为「特征工程」。</p>\n\n<p>可以看出来,「特征工程」的人工工作量非常大,可以说是名副其实的「人工」智能了(此处捂脸)。但这已经比此前的、有点理想的那种构建符号系统的想法,要务实多了,也确实在解决问题的实用主义上也好得多。以这个为主流的研究,大概持续到 2010 年代。</p>\n\n<h4 id=\"3撇开特征让机器囫囵吞枣地学吧\">3、撇开特征,让机器「囫囵吞枣」地学吧</h4>\n\n<p>要经过「人工」对特征进行研究、提取,实在是太难了,你说是「经验主义」,其实我个人认为有点介于「理性主义」与「经验主义」之间。毕竟还是非常需要人进行非常专家级地梳理的。于是,更囫囵个儿地给机器喂数据,让机器学会的方向,逐渐成为主流。能这样的前提,是牛逼算力的大发展,以及海量数据集的大规模沉淀,所以才会在 2010 年代爆发。</p>\n\n<p>这囫囵吞枣的学法,目前主要都是基于深度神经网路的表示学习方法实现的。为啥说「深度神经网络」,因为「从输入到输出」是有一层又一层的神经网络,第一层接收原始的自然语言输入,这么多层的神经网络就被称为深度神经网络。这个过程显著地避免了「特征工程」的人工高成本。</p>\n\n<h4 id=\"4囫囵个儿地学习省去特征工程的人工但也少不了标注的人工\">4、囫囵个儿地学习,省去特征工程的人工,但也少不了标注的人工</h4>\n\n<p>虽然省去了需要专家的「特征工程」,但是这个「囫囵个儿学习法」还是需要依赖标注数据的,也就是「监督学习」。通过先学习大量有人工标注地数据,构建好深度神经网络后,再对测试数据进行验证,最后再用于使用。能不能把人工标注也给省了?或者至少不需要海量标注吧。</p>\n\n<h4 id=\"5自监督学习法让我们省去人工标注\">5、自监督学习法,让我们省去人工标注</h4>\n\n<p>大家上中学的时候做过英语试卷里的「完形填空」吗?为什么我们根据一个填空的上下文,能推测出这个空应该填什么词?那我们是不是可以根据这个原理,把一段段完整的文字内容挖词进行训练学习?没错,这个挖掉的词,就可以当做曾经的「人工标注」,上年文就是训练数据。但是需要海量的数据,怎么办?</p>\n\n<p>好在书籍、互联网网页是我们最好的数据来源,而且数据量极其巨大,于是这就解决了人工个标注问题。由此衍生出来的方法,就被成为「自监督学习(Self-Supervised Learning)」。</p>\n\n<h4 id=\"6用原始的任务训练出来的模型能迁移去解决新任务吗\">6、用原始的任务训练出来的模型,能迁移去解决新任务吗?</h4>\n\n<p>这是一个迁移学习问题,这也就引出了「预训练(Pre-Training)」,最近火到出圈的「ChatGPT」最后两个字母「PT」就是「预训练」。正如「预训练」这个名字,我们先对一些原始任务用大量数据对一个模型进行训练(这个过程其实就叫预训练),然后对于实际要解决的各种任务,再使用少量数据对模型进行精调(Fine-Tune),从而得到一个解决具体问题的模型。</p>\n\n<p>这样的方式,让面对具体任务(可以叫下游任务,或者目标任务)时可以省去很多训练,所以对这种模型叫做「预训练模型」。因此上游任务的训练,就变得非常有复用性、通用性价值,而不是每次面对新任务构建新模型来训练。沿着预训练模型,NLP 取得了非常多的突破。这个技术趋势,是从 2017 年 Transformer 模型在论文《Attention is All You Need》被提出后开始的,在论文中作者使用了大量的未标记的语言数据进行自监督学习,以学习 Transformer 模型的语言表示。然后,在这个自监督学习的模型的基础上,再使用少量的标记数据进行进一步训练,以解决具体的目标任务。</p>\n\n<h4 id=\"7从理解到生成nlp-是最直面-aigc-最硬核难题的领域\">7、从理解到生成,NLP 是最直面 AIGC 最硬核难题的领域</h4>\n\n<p>我们再说回到前面提到的人工标注,从这点来理解所谓「任务」。人工标注,是主观性很强的。在图像处理、语音识别两个领域,标注数据的复用性很强,所以可以积累大的数据标注集,这是有积累沉淀价值的,比如 CV 领域鼎鼎大名的 ImageNet 图像数据集。但是 NLP 领域的任务复杂、多样,很难像图像处理、语音识别那样单纯地得到大量有价值标注。什么意思呢?这与我们在不同领域面对的任务有关。</p>\n\n<p>比如给一副画,对于绝大多数需要输入这幅画的任务来说,标注出它是一副油画、作者梵高、画中有星空等等,都是必须的。比如对于一个人脸识别,哪里是眼睛、鼻子、嘴巴,也是从任务层面非常通用的。语音识别就更有通用性了。但是对于一句自然语言,一个随机的任务需要什么信息,这非常难以沉淀通用。</p>\n\n<p>从这个角度说,一个「图像处理」任务一般是要输出这个图像里有什么内容,一个「语音识别」任务一般是要输出这段语音的文字内容是什么。但是一个「自然语言处理」任务一般是要干嘛?鬼知道要干嘛,但肯定大多数时候是要先生成一段话作为回应,这也就是「自然语言生成」。</p>\n\n<p>所以 NLP 领域的 NLG(Natural Language Generation)面对着最多可能性的任务,也就是最直面 AIGC 核心问题的领域。</p>\n\n<h4 id=\"8数据和算力有了还不够\">8、数据和算力有了,还不够</h4>\n\n<p>我个人认为,预训练这个方向之所以正确,就是因为它在推动 AGI(Artificial General Intelligent)。这背后是一个基本哲学问题:我们应该把劲儿使在推动 AGI,还是应该认为每个领域都应该有自己独有的模型?</p>\n\n<p>这个问题的答案,在我看来是笃定的。AI 目前面对的还是人类思考的问题,而人面对的问题去构建的人脑学习模型,并没有呈现出在不同领域里人脑的学习方式有显著差异,更何况计算机能容纳的学习能力显然更广、更深。因此我很笃定,我们一定是要构建 AGI,为什么 AGI 将解决我们方方面面的问题。</p>\n\n<p>那么一个预训练模型,在下游能解决的问题越广,越说明这是在构建 AGI。但是反过来对上游的预训练模型的要求,就是它最好模型参数越多越好,这样能容纳的下游任务也就可能越多样。因此我们现在知道的 ChatGPT 背后的 OpenAI 公司此前研发的 GPT-3 已经有 1750 亿个参数了,这就是 —— 大模型。</p>\n\n<p>所以目前沿着预训练方向发展的自然语言处理领域,已经进入了「大模型、大数据、大算力」时代。</p>\n\n<h3 id=\"二学术里程碑几篇重量级论文\">二、学术里程碑:几篇重量级论文</h3>\n\n<p>以下重量级的论文,每一篇都不短,B 站上有一些二手解读,虽然二手但是也值得高效地看下,这些论文我罗列如下。我的理解也不深,欢迎随时交流。</p>\n\n<h4 id=\"0提出-attention-机制的neural-machine-translation-by-jointly-learning-to-align-and-translate2015\">0、提出 Attention 机制的《Neural Machine Translation by Jointly Learning to Align and Translate》(2015)</h4>\n\n<p>Bahdanau 等人在 2015 年提出了 Attention 机制,论文地址:<a href=\"https://arxiv.org/pdf/1409.0473.pdf\">https://arxiv.org/pdf/1409.0473.pdf</a></p>\n\n<h4 id=\"1提出-transformer-的attention-is-all-you-need2017\">1、提出 Transformer 的《Attention is All You Need》(2017)</h4>\n\n<p>论文地址:<a href=\"https://arxiv.org/pdf/1706.03762.pdf\">https://arxiv.org/pdf/1706.03762.pdf</a></p>\n\n<p>Google 的 Lamda、BERT,OpenAI 的 GPT-3 都是基于 Transformer 的。</p>\n\n<p>《Attention is all you need》是一篇颇具影响力的自然语言处理(NLP)论文,由 Google 在 2017 年发表。这篇论文提出了一种叫做 Transformer 的模型架构,这种模型架构不依赖于递归神经网络(RNN)或卷积神经网络(CNN)等传统的深度学习架构,而是使用了注意力机制(attention mechanism)和多头注意力(multi-head attention)来捕捉序列间的依赖关系。</p>\n\n<p>看到有人说「<strong>Transformer 基本宣告了 LSTM 在 NLP 领域的终结</strong>」。Transformer 模型在 NLP 领域内获得了广泛的应用,并且因为其较好的并行化能力,在计算资源有限的情况下也能够获得较好的性能。Transformer 模型也被广泛应用于其他领域,如计算机视觉、音频处理等。</p>\n\n<h4 id=\"2elmo-deep-contextualized-word-representations\">2、ELMo: Deep contextualized word representations</h4>\n\n<p>ELMo 是 Embeddings from Language Models 的缩写,刚好是《芝麻街》中一个角色的名字,是在 Peters 等人于 2018 年在 ACL(美国计算机学会计算语言学会议,NLP 领域顶级会议之一)上发表的论文《Deep contextualized word representations》中被提出来的。</p>\n\n<p>ELMo 是一种预训练模型,基于深度双向递归神经网络(biLSTM),可以用来生成词嵌入(word embeddings)。ELMo 使用了大量未标记的文本数据训练,并使用了多层双向递归神经网络来学习。</p>\n\n<h4 id=\"3gpt-1improving-language-understanding-by-generative-pre-training\">3、GPT-1:Improving Language Understanding by Generative Pre-Training</h4>\n\n<h4 id=\"4bert-pre-training-of-deep-bidirectional-transformers-for-language-understanding2018\">4、BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding(2018)</h4>\n\n<p>BERT 模型是在一篇于 2018 年发表的叫做《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》的论文中被提出来的,BERT 是 Bidirectional Encoder Representations from Transformers 的缩写。我觉得这个名字有点硬凑出来的意思,BERT 也是《芝麻街》里一个角色的名字,我想就是为了跟 ELMo 凑一块儿怕它孤单吧。这篇论文带来的最大突破性变化有:</p>\n\n<ul>\n <li>在语言模型预训练中引入双向信息:传统的预训练语言模型(比如 word2vec、GloVe)通常只考虑了单向的信息(前面的词语)。BERT 模型则同时考虑了前后的词语,从而更好地捕捉句子的上下文信息。</li>\n <li>在预训练中引入自监督学习任务。</li>\n</ul>\n\n<p>关于 BERT,我这里写了一篇背景介绍、用例试跑、优劣势分析:<a href=\"https://www.mikecaptain.com/2022/12/17/ai-bert-1/\">《你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例》</a></p>\n\n<h4 id=\"5gpt-2\">5、GPT-2:</h4>\n\n<h4 id=\"6gpt-3-language-models-are-few-shot-learners2020\">6、GPT-3: Language Models are Few-Shot Learners(2020)</h4>\n\n<p>这篇来自 OpenAI 的论文,提出了「小样本学习(Few-Shot Learning,FSL)」的新训练方法,可以在小样本的情况下取得优秀的表现。</p>\n\n<h4 id=\"7instructgpt\">7、InstructGPT</h4>\n\n<h4 id=\"其他的重量级论文\">其他的重量级论文</h4>\n\n<ul>\n <li>Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context(2019)</li>\n <li>RoBERTa: A Robustly Optimized BERT Pretraining Approach(2019)</li>\n <li>T5: Exploring the Limits of Transfer Learning witha Unified Text-to-Text Transformer(2020)</li>\n <li>ViT: An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale(2021)</li>\n <li>ERNIE-ViL: Vision and Language Pre-training for Image Captioning and VQA(2021)</li>\n <li>……</li>\n</ul>\n\n<h3 id=\"三行业里程碑\">三、行业里程碑</h3>\n\n<p>2017 年 8 月,Andrej Karpathy 在其 Twitter 上发文称「很遗憾,梯度下降(实现的 AI 模型)代码写得比你好」。同年 11 月 Andrej 在博客上表示,软件 2.0 将会区别于软件 1.0 时代,程序将由更抽象的、基于神经网络权重的程序语言编写。</p>\n\n<p>2018 年 OpenAI 推出了无监督的、基于强化学习的第一代 GPT。</p>\n\n<p>2019 年情人节,OpenAI 发布 GPT-2,当时被称为史上最强的「通用」自然语言处理模型,基于 Transformer,拥有 15 亿个参数,使用含有 800 万网页内容的数据集训练。</p>\n\n<p>2020 年 6 月,拥有 1750 亿个参数的 GPT-3 面世,这个模型的训练量是 GPT-2 的十倍不止,并开放了商业化 API 共使用,不到一年时间发展出约 300 家企业客户。</p>\n\n<p>2021 年 1月,Google 推出 Switch Transformer 模型,参数量 1.6 万亿,是人类首个万亿级参数的语言模型。</p>\n\n<p>2021 年 6 月,微软与 OpenAI 共同推出代码辅助生成 AI 工具 GitHub Copilot.</p>\n\n<p>2022 年 1 月,OpenAI 发布基于 GPT-3 微调的模型 InstructGPT(包括 text-davinci-001、text-davinci-002、text-davinci-003),微调主要来自于 RLHF(Reinforcement Learning via Human Feedback)。</p>\n\n<p>2022 年 5 月,杭州 AI 领域初创公司「感知阶跃(ZMO.ai)」宣布完成由高瓴资本领投、GGV Capital 和 GSR Ventures 跟投的 800 万美元 A 轮融资。</p>\n\n<p>2022 年 10 月 19 日,Jasper.ai 宣布完成由 Insight Partner 领投,Coatue、(BVP)Bessemer 以及 IVP 等机构跟投的 1.25 亿美元 A 轮融资,估值达到了 15 亿美元,Jasper AI 从产品上线至今仅 18 个月。</p>\n\n<p>2022 年 11 月底,OpenAI 推出基于 GPT-3.5 的 ChatGPT 对话系统,震惊全球。项目地址:https://chat.openai.com 。</p>\n\n<p>2022 年 12 月底,专注于各 AI 闭源项目的逆向工程的 Philip Wang 发布了 PaLM+RLHF 的文本生成开源模型,类似于 ChatGPT。该项目基于 Google 的大型语言模型 PaLM 和带有人类反馈的强化学习(RLHF),拥有 5400 亿个参数。项目地址:https://github.com/lucidrains/PaLM-rlhf-pytorch 。</p>\n\n<h3 id=\"四成本\">四、成本</h3>\n\n<p>目前成本主要有三方面:大模型、大数据、大算力。这其中最昂贵的成本首先是算力。下面有几个数据可以作为参照:</p>\n\n<ul>\n <li>2020 年的一项研究表明,开发一个只有 15 亿个参数的文本生成模型的费用高达 160 万美元。</li>\n <li>2022 年 7 月,为了训练拥有 1760 亿个参数的开源模型 Bloom,Hugging Face 的研究人员耗时三个月,使用了 384 个英伟达 A100 GPU。</li>\n <li>OpenAI 的文本生成 GPT-3(具有大约 1750 亿个参数)的运行成本约为每年 87,000 美元。</li>\n <li>Hugging Face 训练 Bloom 花了三个月的时间。</li>\n</ul>\n\n<h3 id=\"五业内应用\">五、业内应用</h3>\n\n<p>因为图片生成的容错率非常高,也就是在应用上的包容度更高,相比之下文本或语音的生成,是对结果容错非常低的,比如不容许事实错误、逻辑错误等等。这类的应用,我们能想到:</p>\n\n<ul>\n <li>虚拟客服(可以乱真的)</li>\n <li>智能助理:AI 家庭教师、AI 非诉律师、AI 医生助手、AI 新闻编辑、AI 设计助理</li>\n <li>智能翻译</li>\n <li>智能导购员:如果叠加虚拟人技术、语音合成技术,可以应用于电商</li>\n <li>AI 广告公司:替代传统广告公司</li>\n <li>AI 程序员助手:更高智能的辅助代码生成</li>\n <li>部分场景下的美术工作者:游戏素材生成、海报生成</li>\n</ul>\n\n<p>我们可以看到,AI 带来的这一波机会,都是曾经常说的「人不会被 AI 替代」的领域,也就是一些创作创意创新型工作,其中的中低端部分会因为成本因素而极力推动 AI 应用的发展。</p>\n\n<p>所以下面除了大家耳熟能详的 CV 领域的 AIGC 产品 Disco Diffusion、MidJourney、DALL·E 2、Stable Diffusion 之外,我们重点关注非图片生成类的应用。</p>\n\n<ul>\n <li>用于营销场景的 AI 写手与图像生成工具「<strong>Jasper.ai</strong>」,常被用于生成互联网营销文案(比如用于 Instagram、Tik Tok、Facebook、博客、email、论坛帖子 等等)。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-7.png\" alt=\"image\" /></p>\n\n<ul>\n <li>2021 年 6 月,微软与 OpenAI 共同推出的的代码辅助生成 AI 工具「<a href=\"https://github.com/features/copilot\">GitHub Copilot</a>」发布。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-2.jpg\" alt=\"image\" /></p>\n\n<ul>\n <li>文案神器「<strong>Copy.ai</strong>」:</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-9.png\" alt=\"image\" /></p>\n\n<ul>\n <li>虚拟客服「<strong>DialogFlow</strong>」,能理解电话、语音内容等输入,并且给出文本或语音合成的输出。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-8.png\" alt=\"image\" /></p>\n\n<ul>\n <li>2021 年年底,西湖心辰公司发布「<a href=\"https://www.heyfriday.cn/\">Friday AI 智能协作系统</a>」,并且目前也做了商业化。</li>\n</ul>\n\n<p><img src=\"/img/src/2022-12-24-captain-nlp-1.png\" alt=\"image\" /></p>\n\n<h3 id=\"五行业内哪些人的言论值得我们日常重点关注\">五、行业内哪些人的言论值得我们日常重点关注</h3>\n\n<p>这些人的言论都值得我们关注:Sam Altman、Andrej Karpathy、Elon Musk。</p>\n\n<p>Andrej Karpathy 在其 Medium 博客上提到:</p>\n\n<blockquote>\n <p>我们都熟悉的软件 1.0 的「经典堆栈」(The classical stack)是由 Python、C++ 等语言编写的,它由程序员编写的明确的计算机指令组成。通过编写每一行代码,程序员标识了程序空间中具有某些期望行为的特定点。</p>\n</blockquote>\n\n<blockquote>\n <p>相比之下,软件 2.0 是用更抽象、不友好的人类语言(如神经网络的权重)编写的,没有人参与编写这些代码,因为权重数量很多(典型的网络可能有数百万个),并且直接用权重编写代码有一定困难(我尝试过)。</p>\n</blockquote>\n\n<p>不过打那之后 Andrej 在其博客上就再未说过一句话。</p>\n\n<p>OpenAI 创始人兼 CEO Sam Altman 曾表示:</p>\n\n<blockquote>\n <p>十年前的传统观点认为,人工智能首先会影响体力劳动,然后是认知劳动,再然后,也许有一天可以做创造性工作。现在看起来,它会以相反的顺序进行。</p>\n</blockquote>\n\n<blockquote>\n <p>通用人工智能的建成会比大多数人想象得更快,并且它会改变大多数人想象中的一切。」</p>\n</blockquote>\n\n<p>另外还有一个喜欢写博客的 AI 从业者,其博客值得我们学习与了解,就是 OpenAI 应用人工智能研究负责人 Lilian Weng,主要从事机器学习、深度学习和网络科学研究。她本科毕业于香港大学,硕士就读于北京大学信息系统与计算机科学系,之后前往印度安纳大学布鲁顿分校攻读博士。</p>\n\n<p>她的 Blog:<a href=\"https://lilianweng.github.io/\">https://lilianweng.github.io/</a>\n她的 Twitter:<a href=\"https://twitter.com/lilianweng\">https://twitter.com/lilianweng</a></p>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>https://beta.openai.com/docs/models</li>\n <li>https://karpathy.medium.com/software-2-0-a64152b37c35</li>\n <li>https://hub.baai.ac.cn/view/21726</li>\n <li>https://www.reddit.com/r/OpenAI/comments/zdrnsf/comment/iz3kfui/?context=3</li>\n <li>https://www.sohu.com/a/615541698_121255906</li>\n <li>http://blog.itpub.net/29829936/viewspace-2654536/</li>\n <li>http://tech.sina.com.cn/csj/2018-10-13/doc-ihmhafir3634167.shtml</li>\n <li>https://colab.research.google.com/github/alembics/disco-diffusion/blob/main/Disco_Diffusion.ipynb#scrollTo=DefMidasFns</li>\n <li>https://en.wikipedia.org/wiki/BERT_(language_model)</li>\n <li>https://www.mikecaptain.com/2022/12/17/ai-bert-1/</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例</title>\n \t<meta name=\"description\" content=\"2018 年 Google 发布了 BERT 模型后迅速席卷 NLP 领域,这家伙可是比 ChatGPT 背后的 GPT 还要早的。本文简单介绍了 BERT 后主要是希望大家都手试一下,所以文中提到了一个小的中文模型供大家练手,以及一个小用例。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>你可能已经听说 GPT-3,但是你也不能不知道 BERT —— 跟我一起用 BERT 跑个小用例</h2>\t\t\n\t<time datetime=\"2022-12-17T15:08:01+00:00\" class=\"by-line\">17 Dec 2022, 杭州 | 麦克船长 | 总计 7275 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一关于-bert-的一些背景\" id=\"markdown-toc-一关于-bert-的一些背景\">一、关于 BERT 的一些背景</a></li>\n <li><a href=\"#二开始一个-bert-的动手小试验\" id=\"markdown-toc-二开始一个-bert-的动手小试验\">二、开始一个 BERT 的动手小试验</a> <ul>\n <li><a href=\"#1安装-anaconda-来为部署-bert-做环境准备\" id=\"markdown-toc-1安装-anaconda-来为部署-bert-做环境准备\">1、安装 Anaconda 来为部署 BERT 做环境准备</a></li>\n <li><a href=\"#2安装-bert-所需要的各种依赖\" id=\"markdown-toc-2安装-bert-所需要的各种依赖\">2、安装 BERT 所需要的各种依赖</a></li>\n <li><a href=\"#3下载一个预训练pre-train过的-bert-模型\" id=\"markdown-toc-3下载一个预训练pre-train过的-bert-模型\">3、下载一个预训练(Pre-Train)过的 BERT 模型</a></li>\n <li><a href=\"#5启动-bert-服务端\" id=\"markdown-toc-5启动-bert-服务端\">5、启动 BERT 服务端</a></li>\n <li><a href=\"#6在-pycharm-中使用-conda-的环境\" id=\"markdown-toc-6在-pycharm-中使用-conda-的环境\">6、在 PyCharm 中使用 Conda 的环境</a></li>\n <li><a href=\"#7编写程序实现-bert-客户端\" id=\"markdown-toc-7编写程序实现-bert-客户端\">7、编写程序实现 BERT 客户端</a></li>\n </ul>\n </li>\n <li><a href=\"#三bert-模型的优劣势及其原因\" id=\"markdown-toc-三bert-模型的优劣势及其原因\">三、BERT 模型的优劣势及其原因</a> <ul>\n <li><a href=\"#1bert-的优势是很明显的\" id=\"markdown-toc-1bert-的优势是很明显的\">1、BERT 的优势是很明显的</a> <ul>\n <li><a href=\"#11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\" id=\"markdown-toc-11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\">1.1、MLM 和 NSP 预训练能够捕捉到自然语言中的各种复杂细节</a></li>\n <li><a href=\"#12识别并专注于较重要的部分进行文本处理\" id=\"markdown-toc-12识别并专注于较重要的部分进行文本处理\">1.2、识别并专注于较重要的部分进行文本处理</a></li>\n <li><a href=\"#13快速构建针对具体任务的-nlp-系统\" id=\"markdown-toc-13快速构建针对具体任务的-nlp-系统\">1.3、快速构建针对具体任务的 NLP 系统</a></li>\n </ul>\n </li>\n <li><a href=\"#2bert-模型的劣势及其原因\" id=\"markdown-toc-2bert-模型的劣势及其原因\">2、BERT 模型的劣势及其原因</a> <ul>\n <li><a href=\"#21随机挖-mask-的完形填空题是有隐患的\" id=\"markdown-toc-21随机挖-mask-的完形填空题是有隐患的\">2.1、随机挖 MASK 的完形填空题是有隐患的</a></li>\n <li><a href=\"#22nsp-任务有必要吗\" id=\"markdown-toc-22nsp-任务有必要吗\">2.2、NSP 任务有必要吗?</a></li>\n <li><a href=\"#23针对两个或以上词组成的连续词的词义被丢失\" id=\"markdown-toc-23针对两个或以上词组成的连续词的词义被丢失\">2.3、针对两个或以上词组成的连续词的词义被丢失</a></li>\n <li><a href=\"#24需要的算力高\" id=\"markdown-toc-24需要的算力高\">2.4、需要的算力高</a></li>\n <li><a href=\"#25需要的模型大\" id=\"markdown-toc-25需要的模型大\">2.5、需要的模型大</a></li>\n </ul>\n </li>\n </ul>\n </li>\n <li><a href=\"#四一些关于-bert-的问题\" id=\"markdown-toc-四一些关于-bert-的问题\">四、一些关于 BERT 的问题</a> <ul>\n <li><a href=\"#1bert-模型的所谓双向与-bilstm-的双向是啥区别\" id=\"markdown-toc-1bert-模型的所谓双向与-bilstm-的双向是啥区别\">1、BERT 模型的所谓「双向」与 BiLSTM 的「双向」是啥区别?</a></li>\n <li><a href=\"#2为什么-bert-可以比-rnn-更好地并行化\" id=\"markdown-toc-2为什么-bert-可以比-rnn-更好地并行化\">2、为什么 BERT 可以比 RNN 更好地并行化</a></li>\n </ul>\n </li>\n <li><a href=\"#reference\" id=\"markdown-toc-reference\">Reference</a></li>\n</ul>\n\n<h3 id=\"一关于-bert-的一些背景\">一、关于 BERT 的一些背景</h3>\n\n<p>2018 年 Google 发布 BERT 后迅速在 NLP 领域引起广泛关注。BERT(Bidirectional Encoder Representations from Transformers)是一种自然语言处理(NLP)的深度学习模型,它可以进行语言模型预测、序列标注和问答等任务。BERT 采用双向的 Transformer 编码器架构,使用了大量的数据和计算资源进行训练,因此具有较强的泛化能力。</p>\n\n<p>BERT 的训练方法是通过让模型对给定的输入文本进行自监督学习,即使用未标记的语料进行训练。BERT 可以在很多 NLP 任务中获得较好的性能,并且由于其双向的编码方式,能够更好地理解语境信息。</p>\n\n<p>BERT 的训练需要大量的计算资源,因此它常常被用来作为解决 NLP 问题的预训练模型,可以用来初始化其他模型的权重,使得这些模型能够更快速地收敛。</p>\n\n<h3 id=\"二开始一个-bert-的动手小试验\">二、开始一个 BERT 的动手小试验</h3>\n\n<p>为了让 conda 使用 Python 3.7,你可以按照这些步骤来操作。</p>\n\n<h4 id=\"1安装-anaconda-来为部署-bert-做环境准备\">1、安装 Anaconda 来为部署 BERT 做环境准备</h4>\n\n<p>先了解几个概念:Anaconda 是一个软件包管理系统,其中包含了 conda 和许多其他的工具。Conda 是 Anaconda 中的一个组件,用于安装和管理软件包。\n我们需要用 conda 创建一个环境,在这个环境里去启用我们想要使用的 BERT 所需要的各种依赖。</p>\n\n<p>更新 conda 到最新版本:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda update <span class=\"nt\">-n</span> base conda\n</code></pre></div></div>\n\n<p>使用 Python 3.7 创建一个新的环境:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda create <span class=\"nt\">-n</span> py37 <span class=\"nv\">python</span><span class=\"o\">=</span>3.7\n</code></pre></div></div>\n\n<p>激活这个新环境:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda activate py37\n</code></pre></div></div>\n\n<p>验证正在使用的是正确版本的 Python</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>python <span class=\"nt\">--version</span>\n</code></pre></div></div>\n\n<p>另外你可能还会用到的 conda 命令有:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 你之后一定会需要 deactivate 一个环境,命令如下:</span>\nconda deactivate py37\n\n<span class=\"c\"># 查看 conda 当前安装的所有库</span>\nconda list\n</code></pre></div></div>\n\n<h4 id=\"2安装-bert-所需要的各种依赖\">2、安装 BERT 所需要的各种依赖</h4>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>conda <span class=\"nb\">install </span><span class=\"nv\">tensorflow</span><span class=\"o\">==</span>1.14.0\n</code></pre></div></div>\n\n<p>验证 tensorflow 是否安装正确:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">import</span> <span class=\"nn\">tensorflow</span> <span class=\"k\">as</span> <span class=\"n\">tf</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">tf</span><span class=\"p\">.</span><span class=\"n\">__version__</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<h4 id=\"3下载一个预训练pre-train过的-bert-模型\">3、下载一个预训练(Pre-Train)过的 BERT 模型</h4>\n\n<p>官方的模型在这里浏览:https://github.com/google-research/bert#pre-trained-models</p>\n\n<p>也有一些中文的模型,以下是 ChatGPT 推荐的三个:</p>\n\n<ul>\n <li>BERT-Base, Chinese:这是 Google 官方提供的中文 BERT 模型,在中文 NLP 任务中表现良好。你可以从 这里下载这个模型。</li>\n <li>ERNIE:这是由中科院自然语言所提供的中文 BERT 模型,包含了额外的语义信息。你可以从 这里下载这个模型。</li>\n <li>RoBERTa-wwm-ext:这是由清华大学自然语言处理实验室提供的中文 BERT 模型,在多种中文 NLP 任务中表现良好。你可以从 这里下载这个模型。</li>\n</ul>\n\n<p>4、安装 BERT 的服务端和客户端</p>\n\n<p>这里我们使用 bert-as-service,bert-as-service 是一种将 BERT 模型部署为服务的方式。该工具使用 TensorFlow Serving 来运行 BERT 模型,并允许通过 REST API 进行调用。根据 bert-as-service 的文档,它已经在 TensorFlow 1.14.0 上测试过。</p>\n\n<p>在你激活的环境里,安装 <code class=\"language-plaintext highlighter-rouge\">bert-as-service</code>:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 安装服务端和客户端</span>\n<span class=\"c\"># 更多关于 bert-serving-server 的信息可以参考:https://bert-serving.readthedocs.io/en/latest/index.html</span>\nconda <span class=\"nb\">install </span>bert-serving-server bert-serving-client \n验证 bert-as-service 是否安装成功\nbert-serving-start <span class=\"nt\">-h</span>\n</code></pre></div></div>\n\n<h4 id=\"5启动-bert-服务端\">5、启动 BERT 服务端</h4>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># 命令行下启动BERT服务</span>\n<span class=\"c\"># -num_worker 表示启动几个worker服务,即可以处理几个并发请求,超过这个数字的请求将会在LBS(负载均衡器)中排队等待</span>\nbert-serving-start <span class=\"nt\">-model_dir</span> /模型/的/绝对/路径 <span class=\"nt\">-num_worker</span><span class=\"o\">=</span>4\n</code></pre></div></div>\n\n<h4 id=\"6在-pycharm-中使用-conda-的环境\">6、在 PyCharm 中使用 Conda 的环境</h4>\n\n<p>在 PyCharm 中启用 Interpreter 为 Anaconda,macOS 上具体地是在「Preference - Project - Python Interpreter - Add Interpreter - Add Local Interpreter - Conda Environment」。</p>\n\n<p>接下来还有一项重要的步骤就是选择该 project 要加载包文件的路径。如果不进行这一步,那该 project 还是从系统环境变量中的路径来搜索你要加载的包,这样在你用 Anaconda 新建的这个环境中所特有的包就会出现无法加载的问题。单击菜单栏 Run 选择 Edit Configuration。在Environment variables中添加一个新的 Path。新的路径为你用 Anaconda 新建的环境的文件夹中的<code class=\"language-plaintext highlighter-rouge\">「/Users/captain/opt/anaconda3/bin/python」</code>。</p>\n\n<p>配置 PyCharm 这里参考:https://docs.anaconda.com/anaconda/user-guide/tasks/pycharm/</p>\n\n<h4 id=\"7编写程序实现-bert-客户端\">7、编写程序实现 BERT 客户端</h4>\n\n<p>这里有一些客户端例子可以参考:https://blog.csdn.net/qq_18256855/article/details/123860126</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kn\">from</span> <span class=\"nn\">bert_serving.client</span> <span class=\"kn\">import</span> <span class=\"n\">BertClient</span>\n<span class=\"kn\">import</span> <span class=\"nn\">numpy</span> <span class=\"k\">as</span> <span class=\"n\">np</span>\n\n<span class=\"c1\"># 定义类\n</span><span class=\"k\">class</span> <span class=\"nc\">BertModel</span><span class=\"p\">:</span>\n <span class=\"k\">def</span> <span class=\"nf\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span> <span class=\"o\">=</span> <span class=\"n\">BertClient</span><span class=\"p\">(</span><span class=\"n\">ip</span><span class=\"o\">=</span><span class=\"s\">'127.0.0.1'</span><span class=\"p\">,</span> <span class=\"n\">port</span><span class=\"o\">=</span><span class=\"mi\">5555</span><span class=\"p\">,</span> <span class=\"n\">port_out</span><span class=\"o\">=</span><span class=\"mi\">5556</span><span class=\"p\">)</span> <span class=\"c1\"># 创建客户端对象\n</span> <span class=\"c1\"># 注意:可以参考API,查看其它参数的设置\n</span> <span class=\"c1\"># 127.0.0.1 表示本机IP,也可以用localhost\n</span> <span class=\"k\">except</span><span class=\"p\">:</span>\n <span class=\"k\">raise</span> <span class=\"nb\">Exception</span><span class=\"p\">(</span><span class=\"s\">\"cannot create BertClient\"</span><span class=\"p\">)</span>\n\n <span class=\"k\">def</span> <span class=\"nf\">close_bert</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span><span class=\"p\">.</span><span class=\"n\">close</span><span class=\"p\">()</span> <span class=\"c1\"># 关闭服务\n</span>\n <span class=\"k\">def</span> <span class=\"nf\">sentence_embedding</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">text</span><span class=\"p\">):</span>\n <span class=\"s\">'''对输入文本进行embedding\n Args:\n text: str, 输入文本\n Returns:\n text_vector: float, 返回一个列表,包含text的embedding编码值\n '''</span>\n <span class=\"n\">text_vector</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"p\">.</span><span class=\"n\">bert_client</span><span class=\"p\">.</span><span class=\"n\">encode</span><span class=\"p\">([</span><span class=\"n\">text</span><span class=\"p\">])[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n <span class=\"k\">return</span> <span class=\"n\">text_vector</span> <span class=\"c1\"># 获取输出结果\n</span>\n <span class=\"k\">def</span> <span class=\"nf\">caculate_similarity</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">vec_1</span><span class=\"p\">,</span> <span class=\"n\">vec_2</span><span class=\"p\">):</span>\n <span class=\"s\">'''根据两个语句的vector,计算它们的相似性\n Args:\n vec_1: float, 语句1的vector\n vec_2: float, 语句2的vector\n Returns:\n sim_value: float, 返回相似性的计算值\n '''</span>\n <span class=\"c1\"># 根据cosine的计算公式\n</span> <span class=\"n\">v1</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mat</span><span class=\"p\">(</span><span class=\"n\">vec_1</span><span class=\"p\">)</span>\n <span class=\"n\">v2</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">mat</span><span class=\"p\">(</span><span class=\"n\">vec_2</span><span class=\"p\">)</span>\n <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span class=\"p\">(</span><span class=\"n\">v1</span> <span class=\"o\">*</span> <span class=\"n\">v2</span><span class=\"p\">.</span><span class=\"n\">T</span><span class=\"p\">)</span>\n <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">norm</span><span class=\"p\">(</span><span class=\"n\">v1</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">np</span><span class=\"p\">.</span><span class=\"n\">linalg</span><span class=\"p\">.</span><span class=\"n\">norm</span><span class=\"p\">(</span><span class=\"n\">v2</span><span class=\"p\">)</span>\n <span class=\"n\">cosine</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">/</span> <span class=\"n\">b</span>\n <span class=\"k\">return</span> <span class=\"n\">cosine</span>\n\n\n<span class=\"k\">if</span> <span class=\"n\">__name__</span> <span class=\"o\">==</span> <span class=\"s\">\"__main__\"</span><span class=\"p\">:</span>\n <span class=\"c1\"># 创建bert对象\n</span> <span class=\"n\">bert</span> <span class=\"o\">=</span> <span class=\"n\">BertModel</span><span class=\"p\">()</span>\n <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n <span class=\"c1\"># --- 输入语句 ----\n</span> <span class=\"n\">input_a</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s\">'请输入语句1: '</span><span class=\"p\">)</span>\n\n <span class=\"k\">if</span> <span class=\"n\">input_a</span> <span class=\"o\">==</span> <span class=\"s\">\"N\"</span> <span class=\"ow\">or</span> <span class=\"n\">input_a</span> <span class=\"o\">==</span> <span class=\"s\">\"n\"</span><span class=\"p\">:</span>\n <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">close_bert</span><span class=\"p\">()</span> <span class=\"c1\"># 关闭服务\n</span> <span class=\"k\">break</span>\n\n <span class=\"n\">input_b</span> <span class=\"o\">=</span> <span class=\"nb\">input</span><span class=\"p\">(</span><span class=\"s\">'请输入语句2: '</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># --- 对输入语句进行embedding ---\n</span>\n <span class=\"n\">a_vec</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">sentence_embedding</span><span class=\"p\">(</span><span class=\"n\">input_a</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'a_vec shape : '</span><span class=\"p\">,</span> <span class=\"n\">a_vec</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n\n <span class=\"n\">b_vec</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">sentence_embedding</span><span class=\"p\">(</span><span class=\"n\">input_b</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'b_vec shape : '</span><span class=\"p\">,</span> <span class=\"n\">b_vec</span><span class=\"p\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 计算两个语句的相似性\n</span> <span class=\"n\">cos</span> <span class=\"o\">=</span> <span class=\"n\">bert</span><span class=\"p\">.</span><span class=\"n\">caculate_similarity</span><span class=\"p\">(</span><span class=\"n\">a_vec</span><span class=\"p\">,</span> <span class=\"n\">b_vec</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'cosine value : '</span><span class=\"p\">,</span> <span class=\"n\">cos</span><span class=\"p\">)</span>\n\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">'</span><span class=\"se\">\\n\\n</span><span class=\"s\">'</span><span class=\"p\">)</span>\n\n <span class=\"c1\"># 如果相似性值大于0.85,则输出相似,否则,输出不同\n</span> <span class=\"k\">if</span> <span class=\"n\">cos</span> <span class=\"o\">></span> <span class=\"mf\">0.85</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"2个语句的含义相似\"</span><span class=\"p\">)</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"不相似\"</span><span class=\"p\">)</span>\n</code></pre></div></div>\n\n<p>在使用 <code class=\"language-plaintext highlighter-rouge\">bert-serving-client</code> 连接 <code class=\"language-plaintext highlighter-rouge\">bert-serving-server</code> 时,你需要确保 <code class=\"language-plaintext highlighter-rouge\">bert-serving-server</code> 使用的模型和 <code class=\"language-plaintext highlighter-rouge\">bert-serving-client</code> 使用的模型是匹配的,否则会出现错误。</p>\n\n<p>程序正常运行后,将要求你输入两句话,然后 BERT 计算两句话的相似性。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>请输入语句1: \n请输入语句2: \n</code></pre></div></div>\n\n<p>两句输入好确认后,得到如下形式的结果:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>a_vec shape : (768,)\nb_vec shape : (768,)\ncosine value : 0.8691698561422959\n</code></pre></div></div>\n\n<p>其实这个小试验蛮没意思的,而且准确性也比较令人质疑。</p>\n\n<h3 id=\"三bert-模型的优劣势及其原因\">三、BERT 模型的优劣势及其原因</h3>\n\n<p>论文地址:<a href=\"https://arxiv.org/abs/1810.04805\">《BERT: Pre-Training of Deep Bidirectional Transformers for Language Understanding》</a> 。</p>\n\n<h4 id=\"1bert-的优势是很明显的\">1、BERT 的优势是很明显的</h4>\n\n<p>复旦大学的邱锡鹏教授层评价 BERT 的「里程碑意义」在于:</p>\n\n<blockquote>\n <p>证明了一个非常深的模型可以显著提高 NLP 任务的准确率,而这个模型可以从无标记数据集中预训练得到。</p>\n</blockquote>\n\n<h5 id=\"11mlm-和-nsp-预训练能够捕捉到自然语言中的各种复杂细节\">1.1、MLM 和 NSP 预训练能够捕捉到自然语言中的各种复杂细节</h5>\n\n<p>因为 BERT 采用了双向的自注意力机制,这里的「双向」意味着 BERT 模型可以同时利用输入文本的前后文信息来预测下一个词是什么、下一句是什么。这样 BERT 模型就可以捕捉到自然语言中的各种隐藏的细节,比如语义关系、语法结构、语义暗示等等。</p>\n\n<p>具体地,BERT 采用了 Masked Language Model(MLM)来做「下一个词是什么」的预训练,采用了 Next Sentence Prediction(NSP)来做「下一句是什么」的预训练。MLM 的方式其实就很像英语考试里的「完形填空」,而 NSP 的方式,就像整句的完形填空。</p>\n\n<h5 id=\"12识别并专注于较重要的部分进行文本处理\">1.2、识别并专注于较重要的部分进行文本处理</h5>\n\n<p>这要得益于因为 BERT 采用了自注意力机制。自注意力机制,通过计算输入单元的权重值,来确定在一个输入序列中哪些输入单元是重要的。具体地,一个输入单元与其他单元的相似性越高,按照我们自然语言的逻辑,那么这部分是在被重复、强调、翻来覆去用不同的方式在解释,那么这部分就是重要的,权重值就更高。</p>\n\n<h5 id=\"13快速构建针对具体任务的-nlp-系统\">1.3、快速构建针对具体任务的 NLP 系统</h5>\n\n<p>因为 BERT 采用了预训练模型,能够在没有监督标注数据的情况下从大量文本中学习语言模型。因为我们认为上下文信息本身就能推测出某个词,所以大量的文本数据本身就是一种「自带标注」的数据,所以 BERT 能够无监督学习。</p>\n\n<h4 id=\"2bert-模型的劣势及其原因\">2、BERT 模型的劣势及其原因</h4>\n\n<h5 id=\"21随机挖-mask-的完形填空题是有隐患的\">2.1、随机挖 MASK 的完形填空题是有隐患的</h5>\n\n<p>对于上面提到的 MLM、NSP 方法做预训练,那么问题也就显而易见了,如果我们挖掉的一组 MASK 完形填空词,是强关联的(非条件独立),那么这一组词的预测就都会出现问题。</p>\n\n<h5 id=\"22nsp-任务有必要吗\">2.2、NSP 任务有必要吗?</h5>\n\n<p>论文《Crosslingual language model pretraining》中提到 BERT 的 NSP 可能是非必要的,针对这个问题,后续出现的模型都移除了 NSP 任务,比如 RoBERTa、spanBERT、ALBERT。</p>\n\n<h5 id=\"23针对两个或以上词组成的连续词的词义被丢失\">2.3、针对两个或以上词组成的连续词的词义被丢失</h5>\n\n<p>比如 cutting-edge,MLM 的方式可能会割裂这两个子词的相关性,导致模型丢失这个词的词义,针对这个问题 Google 后来发表了 BERT-WWM,WWM 即 Whole Word Masking,从字面就能理解针对的问题。哈尔滨工业大学的科大讯飞联合实验室后来推出了 Chinese-BERT-WWM 专门针对中文解决了这个问题。</p>\n\n<h5 id=\"24需要的算力高\">2.4、需要的算力高</h5>\n\n<p>算力高,自然需要的计算成本运行更高。不过算力成本高这种问题总有办法优化,通常来说不是模型本身所处理问题的局限性和先决条件的局限性(比如依赖大量人工工作)就非常好了。</p>\n\n<h5 id=\"25需要的模型大\">2.5、需要的模型大</h5>\n\n<p>模型大,自然存储成本也就高了。这也类似于上一点,而且算力、存储成本高,可以在大型应用中把成本均摊下来,比如 BERT 如果支持的某个 AGI 应用得到广泛普及。</p>\n\n<h3 id=\"四一些关于-bert-的问题\">四、一些关于 BERT 的问题</h3>\n\n<h4 id=\"1bert-模型的所谓双向与-bilstm-的双向是啥区别\">1、BERT 模型的所谓「双向」与 BiLSTM 的「双向」是啥区别?</h4>\n\n<p>BiLSTM 是把句子再倒序一遍,而 BERT 的双向是指在 Encoder 的自注意力机制下编码一个 token 时「同时利用上下文」的 token。</p>\n\n<h4 id=\"2为什么-bert-可以比-rnn-更好地并行化\">2、为什么 BERT 可以比 RNN 更好地并行化</h4>\n\n<p>RNN 因为有时序概念,即后面的特征计算,依赖于前面计算的结果,所以就形成了循环(Recurrent)。而 BERT 采用了自注意力机制则没有时序概念,每个词特征都依赖其上下文独立计算,因此更容易并行化。</p>\n\n<h3 id=\"reference\">Reference</h3>\n\n<ol>\n <li>https://arxiv.org/abs/1810.04805</li>\n <li>https://github.com/google-research/bert</li>\n <li>https://github.com/ymcui/Chinese-BERT-wwm</li>\n <li>https://zhuanlan.zhihu.com/p/195723105</li>\n <li>https://www.jiqizhixin.com/articles/2018-10-24-13</li>\n</ol>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</title>\n \t<meta name=\"description\" content=\"最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是「Optimizing Language Models for Dialogue」,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>动动手,让你和你的朋友们,在微信上跟 ChatGPT 聊聊天</h2>\t\t\n\t<time datetime=\"2022-12-11T15:59:57+00:00\" class=\"by-line\">11 Dec 2022, 杭州 | 麦克船长 | 总计 1692 字</time>\n\t<div class=\"content\">\n\t\t<p><img src=\"/img/src/2022-12-11-wechat-chatgpt-3.png\" alt=\"image\" /></p>\n\n<h3 id=\"写在前面\">写在前面</h3>\n<p>最近 OpenAI 的 ChatGPT 非常地出圈,ChatGPT 是一个由 OpenAI 训练的大型语言模型,被设计用来回答用户的问题并提供信息。官方的 Slogan 是 <strong>「Optimizing Language Models for Dialogue」</strong>,所以非常适合做到 IM 里聊天。那么我在想如果用一个微信号,背后是 ChatGPT,是不是很有趣?正当我准备利用 WeChaty 开发一个服务端程序来连接 ChatGPT 时,发现目前 Github 上已经有人做了,刚好可以省去很多工程的工作。</p>\n\n<h3 id=\"stepbystep\">Step by step</h3>\n\n<p>本实践依赖:CLI、Docker、npm、Github、fuergaosi233/wechat-chatgpt、git、YAML、Chrome 的使用。以下将简洁地 Step by step 列出步骤。</p>\n\n<p>第一步,你要现有一个 OpenAI 的账号,注意注册时手机号不能是中国大陆或香港的,IP 地址和 GPS 也不能暴露你是中国大陆或者香港的。</p>\n\n<p>第二步,准备一台服务器(否则个人电脑要一直处于开机运行状态),由于后面将用到 Session Token 来登录,因此 IP 地址是香港也没关系,于是我是在我的香港服务器上部署 wechat-chatgpt</p>\n\n<p>第三步,在服务器上安装 Docker,不赘述。</p>\n\n<p>第四步,从 Github 上拉取项目项目到服务器上。</p>\n\n<p>第五步,任何设备上登录 ChatGPT,用 Chrome 的 Inspect 来查看并复制 session token 到剪贴板。</p>\n\n<p>第六步,编辑 wechat-chatgpt 的 config.yaml,填写 session token;设置 private trigger keywords(可选)。</p>\n\n<div class=\"language-yaml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"na\">chatGPTAccountPool</span><span class=\"pi\">:</span>\n <span class=\"pi\">-</span> <span class=\"na\">email</span><span class=\"pi\">:</span> <span class=\"s\"><your email></span>\n <span class=\"na\">password</span><span class=\"pi\">:</span> <span class=\"s\"><your password></span>\n<span class=\"c1\"># if you hope only some keywords can trigger chatgpt on private chat, you can set it like this:</span>\n<span class=\"na\">chatPrivateTiggerKeyword</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">\"</span>\n</code></pre></div></div>\n\n<p>第七步,用 docker 来拉取 wechat-chatgpt</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker pull holegots/wechat-chatgpt:latest。\n</code></pre></div></div>\n\n<p>第八步,启动 wechat-chatgpt:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker run <span class=\"nt\">-d</span> <span class=\"nt\">--name</span> wechat-chatgpt <span class=\"nt\">-v</span> <span class=\"si\">$(</span><span class=\"nb\">pwd</span><span class=\"si\">)</span>/config.yaml:/app/config.yaml holegots/wechat-chatgpt:latest\n</code></pre></div></div>\n\n<p>注意,如果手动模式下也可以用npm run dev启动。如果提示系统不认识 npm 则可以运行 <code class=\"language-plaintext highlighter-rouge\">npm install && poetry install</code> 来解决。到此你就可以在微信上跟这个打通了 ChatGPT 的账号聊天了。</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-1.png\" alt=\"image\" style=\"width:100%\" /></th>\n <th><img src=\"/img/src/2022-12-11-wechat-chatgpt-2.png\" alt=\"image\" style=\"width:100%\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>其实可以看到这个 AI 船长不管是专业性问题(计算机相关)还是非专业问题,都回答的很不错。</p>\n\n<p>如何停止、重启、查看日志呢?首先停止的命令是docker stop wechat-chatgpt,登录时需要扫码登录微信并追踪 logs,因为这其实是用了微信在桌面端的接口。</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker logs <span class=\"nt\">-f</span> wechat-chatgpt\n</code></pre></div></div>\n\n<p>会在 Terminal 里显示一个文字阵列组成的桌面端微信登录二维码,用你打算做成微信 AI 机器人那个微信号扫一下,相关信息都填完。另外,这样最好别用自己的微信大号,而是用一个小号。微信不让聊这些,小号注意要完成实名认证。</p>\n\n<p>如果要停止运行,用如下命令:</p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>docker stop wechat-chatgpt\n</code></pre></div></div>\n\n<h3 id=\"参考\">参考</h3>\n\n<p>1、<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/tree/main\">https://github.com/fuergaosi233/wechat-chatgpt/tree/main</a></p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</title>\n \t<meta name=\"description\" content=\"AIGC,MidJourney,Image2Text,文生图\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>确实惊艳!用 MidJourney 三分钟生成了两张 CG 级高清机甲特写</h2>\t\t\n\t<time datetime=\"2022-11-30T15:12:03+00:00\" class=\"by-line\">30 Nov 2022, 杭州 | 麦克船长 | 总计 387 字</time>\n\t<div class=\"content\">\n\t\t<p>因为 Diffusion 模型在计算机视觉领域的发展,最近文生图(Text2Image)很火,花了三分钟时间用 MidJourney 做了一组机甲图,确实非常惊艳,直接看图:</p>\n\n<table>\n <thead>\n <tr>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-1.png\" alt=\"image\" /></th>\n <th><img src=\"/img/src/2022-12-16-midjourney-first-test-2.png\" alt=\"image\" /></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td> </td>\n <td> </td>\n </tr>\n </tbody>\n</table>\n\n<p>今年人工智能在 CV 领域的发展非常的精彩,目前市面上看到的主要应用,都是这种松散式的、对结果容错率很高图像生成,基于一段 prompt 生成一张或一组图片,甚至已经有了 avatarai.me 这种帮你打造全套的 photorealistic 层次质感的全套图片和视频商业化产品。</p>\n\n<p><img src=\"/img/src/2022-12-16-midjourney-first-test-3.png\" alt=\"image\" />\n(<em>注:MidJourney 官网</em>)</p>\n\n<p>未来很快,我们将看到一些更精准满足图像生成需求的应用出现,比如生成游戏素材(其实现在已经有了,比如 Scenario.gg)、AI 替身生成等等。</p>\n\n<p>相应的,对抗性的防御技术也会很快发展。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</title>\n \t<meta name=\"description\" content=\"近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做 ……\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>Pathways 语言模型 (PaLM):扩展到 5400 亿个参数以获得突破性性能</h2>\t\t\n\t<time datetime=\"2022-04-06T09:13:09+00:00\" class=\"by-line\">06 Apr 2022, 杭州 | Google Research | [译] AI & 麦克船长 | 总计 5433 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#使用-pathways-训练一个-5400-亿参数的语言模型\" id=\"markdown-toc-使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</a></li>\n <li><a href=\"#语言推理和代码任务的突破性能力\" id=\"markdown-toc-语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</a> <ul>\n <li><a href=\"#语言理解与生成\" id=\"markdown-toc-语言理解与生成\">语言理解与生成</a></li>\n <li><a href=\"#推理\" id=\"markdown-toc-推理\">推理</a></li>\n <li><a href=\"#代码生成\" id=\"markdown-toc-代码生成\">代码生成</a></li>\n </ul>\n </li>\n <li><a href=\"#道德考量\" id=\"markdown-toc-道德考量\">道德考量</a></li>\n <li><a href=\"#结论和未来的工作\" id=\"markdown-toc-结论和未来的工作\">结论和未来的工作</a></li>\n <li><a href=\"#致谢\" id=\"markdown-toc-致谢\">致谢</a></li>\n</ul>\n\n<ul>\n <li>原文标题:Pathways Language Model (PaLM): Scaling to 540 Billion Parameters for Breakthrough Performance</li>\n <li>原文链接:https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html</li>\n <li>原文作者:Google Search 软件工程师 Sharan Narang 和 Aakanksha Chowdhery</li>\n <li>原文日期:2022 年 4 月 4 日</li>\n</ul>\n\n<p>近年来,为语言理解和生成而训练的大型神经网络在广泛的任务中取得了令人瞩目的成果。 GPT-3 首先展示了大型语言模型 (LLM) 可用于少样本学习,无需大规模任务特定数据收集或模型参数更新即可取得令人印象深刻的结果。 最近的 LLM,例如 GLaM、LaMDA、Gopher 和 Megatron-Turing NLG,通过缩放模型大小、使用稀疏激活模块以及在来自更多数据集的更大数据集上进行训练,在许多任务上取得了最先进的小样本结果。 来源多样。 然而,在我们推动模型规模的极限时,要理解小样本学习所出现的能力,还有很多工作要做。</p>\n\n<p>去年,Google Research 宣布了我们对 Pathways 的愿景,这是一种单一模型,可以在高效的同时跨领域和任务进行泛化。 实现这一愿景的一个重要里程碑是开发新的 Pathways 系统来为加速器编排分布式计算。 在“PaLM: Scaling Language Modeling with Pathways”中,我们介绍了 Pathways Language Model (PaLM),这是一个 5400 亿参数、密集解码器的 Transformer 模型,使用 Pathways 系统训练,使我们能够有效地跨多个模型训练单个模型 TPU v4 Pod。 我们在数百个语言理解和生成任务上评估了 PaLM,发现它在大多数任务中都实现了最先进的小样本性能,在许多情况下都有很大的优势。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-1.gif\" alt=\"image\" /></p>\n\n<p>随着模型规模的增加,跨任务的性能会提高,同时还会解锁新功能。</p>\n\n<h2 id=\"使用-pathways-训练一个-5400-亿参数的语言模型\">使用 Pathways 训练一个 5400 亿参数的语言模型</h2>\n\n<p>PaLM 首次大规模使用 Pathways 系统将训练扩展到 6144 个芯片,这是迄今为止用于训练的最大的基于 TPU 的系统配置。 训练在两个 Cloud TPU v4 Pod 之间使用 Pod 级别的数据并行性进行扩展,同时在每个 Pod 内使用标准数据和模型并行性。 与大多数以前的 LLM 相比,这是规模的显着增加,这些 LLM 在单个 TPU v3 Pod(例如 GLaM、LaMDA)上进行训练,使用流水线并行性跨 GPU 集群(Megatron-Turing NLG)扩展到 2240 个 A100 GPU,或者 使用了多个 TPU v3 Pod(Gopher),最大规模为 4096 个 TPU v3 芯片。</p>\n\n<p>PaLM 实现了 57.8% 的硬件 FLOPs 利用率的训练效率,这是 LLM 在此规模上达到的最高水平。 这是由于结合了并行策略和 Transformer 块的重新制定,允许并行计算注意力和前馈层,从而实现 TPU 编译器优化的加速。</p>\n\n<p>PaLM 使用英语和多语言数据集的组合进行训练,这些数据集包括高质量的网络文档、书籍、维基百科、对话和 GitHub 代码。 我们还创建了一个“无损”词汇表,保留所有空格(对代码尤其重要),将词汇外的 Unicode 字符拆分为字节,并将数字拆分为单独的标记,每个标记一个。</p>\n\n<h2 id=\"语言推理和代码任务的突破性能力\">语言、推理和代码任务的突破性能力</h2>\n\n<p>PaLM 在许多非常困难的任务上显示出突破性的能力。 我们在下面重点介绍了语言理解和生成、推理以及与代码相关的任务的几个示例。</p>\n\n<h3 id=\"语言理解与生成\">语言理解与生成</h3>\n\n<p>我们在 29 个广泛使用的英语自然语言处理 (NLP) 任务上评估了 PaLM。 PaLM 540B 在 29 个跨越问答任务(开放域封闭- book variant)、完形填空和句子完成任务、Winograd 风格任务、上下文阅读理解任务、常识推理任务、SuperGLUE 任务和自然语言推理任务。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-2.png\" alt=\"image\" /></p>\n\n<p>↑ 在 29 个基于英语的 NLP 任务上,PaLM 540B 的性能比之前最先进 (SOTA) 的结果有所提高。</p>\n\n<p>除了英语 NLP 任务外,PaLM 在多语言 NLP 基准测试中也表现出色,包括翻译,尽管只有 22% 的训练语料库是非英语的。</p>\n\n<p>我们还在 Beyond the Imitation Game Benchmark (BIG-bench) 上探索了 PaLM 的新兴和未来功能,这是最近发布的包含 150 多个新语言建模任务的套件,并发现 PaLM 实现了突破性的性能。 我们比较了 PaLM 与 Gopher 和 Chinchilla 的性能,在这些任务的 58 个公共子集中取平均值。 有趣的是,我们注意到 PaLM 作为规模函数的性能遵循与先前模型相似的对数线性行为,这表明规模带来的性能改进尚未达到稳定水平。 PaLM 540B 5-shot 的表现也优于人们要求解决相同任务的平均表现。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-3.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 在 58 个 BIG-bench 任务的子集上的缩放行为。</p>\n\n<p>PaLM 在几个 BIG-bench 任务上展示了令人印象深刻的自然语言理解和生成能力。 为了考试例如,该模型可以区分因果关系,理解适当上下文中的概念组合,甚至可以根据表情符号猜测电影。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-4.gif\" alt=\"image\" /></p>\n\n<p>↑ 展示 PaLM 540B 在 BIG-bench 任务上的 1-shot 性能的示例:标记因果关系、概念理解、根据表情符号猜测电影,以及寻找同义词和反事实。</p>\n\n<h3 id=\"推理\">推理</h3>\n\n<p>通过将模型规模与思维链提示相结合,PaLM 在需要多步算术或常识推理的推理任务上显示出突破性的能力。 之前的 LLM,如 Gopher,认为模型规模在提高性能方面的好处较少。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-5.png\" alt=\"image\" /></p>\n\n<p>↑ 标准提示与思维链提示的示例小学数学问题。 思维链提示将多步推理问题的提示分解为中间步骤(以黄色突出显示),类似于人处理它的方式。</p>\n\n<p>我们在三个算术数据集和两个常识推理数据集上观察到 PaLM 540B 结合思维链提示的强大性能。 例如,通过 8 次提示,PaLM 解决了 GSM8K 中 58% 的问题,GSM8K 是数千个具有挑战性的小学水平数学问题的基准,优于之前通过微调 GPT-3 175B 模型获得的 55% 的最高分 具有 7500 个问题的训练集,并将其与外部计算器和验证器相结合。</p>\n\n<p>这个新分数特别有趣,因为它接近 9-12 岁儿童解决问题的平均 60%,他们是问题集的目标受众。 我们怀疑 PaLM 词汇表中的数字单独编码有助于实现这些性能改进。</p>\n\n<p>值得注意的是,PaLM 甚至可以为需要多步逻辑推理、世界知识和深度语言理解的复杂组合的场景生成明确的解释。 例如,它可以为网络上找不到的小说笑话提供高质量的解释。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-6.png\" alt=\"image\" /></p>\n\n<p>↑ PaLM 用两次提示解释了一个原创笑话。</p>\n\n<h3 id=\"代码生成\">代码生成</h3>\n\n<p>LLM 也被证明 [1, 2, 3, 4] 可以很好地泛化到编码任务,例如编写给定自然语言描述的代码(文本到代码),将代码从一种语言翻译成另一种语言,以及修复编译错误 (代码到代码)。</p>\n\n<p>PaLM 540B 在单个模型中跨编码任务和自然语言任务显示出强大的性能,即使它在预训练数据集中只有 5% 的代码。 它的 few-shot 性能尤其出色,因为它与经过微调的 Codex 12B 相当,同时使用的 Python 代码少了 50 倍进行训练。 这一结果强化了之前的发现,即较大的模型可以比较小的模型更有效地采样,因为它们更有效地从其他编程语言和自然语言数据中迁移学习。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-7.gif\" alt=\"image\" /></p>\n\n<p>↑ 在文本到代码任务(例如 GSM8K-Python 和 HumanEval)和代码到代码任务(例如 Transcoder)上经过微调的 PaLM 540B 模型示例。</p>\n\n<p>通过在纯 Python 代码数据集上微调 PaLM,我们还看到了性能的进一步提高,我们称之为 PaLM-Coder。 对于名为 DeepFix 的示例代码修复任务,其目标是修改最初损坏的 C 程序,直到它们成功编译,PaLM-Coder 540B 展示了令人印象深刻的性能,实现了 82.1% 的编译率,优于之前的 71.7% 的现有技术水平 . 这为修复软件开发过程中出现的更复杂的错误提供了机会。</p>\n\n<p><img src=\"/img/src/2022-04-05-pathways-language-model-palm-scaling-to-8.png\" alt=\"image\" /></p>\n\n<p>来自 DeepFix 代码修复任务的示例。 经过微调的 PaLM-Coder 540B 将编译错误(左,红色)修复为可编译的代码版本(右)。</p>\n\n<h2 id=\"道德考量\">道德考量</h2>\n\n<p>最近的研究强调了与接受网络文本培训的法学硕士相关的各种潜在风险。</p>\n\n<p>通过模型卡和数据表等透明工件分析和记录此类潜在的不良风险至关重要,其中还包括有关预期用途和测试的信息。 为此,我们的论文提供了数据表、模型卡和 Responsible AI 基准测试结果,并报告了对数据集和模型输出的偏差和风险的全面分析。 虽然分析有助于概述模型的一些潜在风险,但针对特定领域和任务的分析对于真正校准、情境化和减轻可能的危害至关重要。 进一步了解这些模型的风险和好处是正在进行的研究的主题,同时开发可扩展的解决方案可以防止恶意使用语言模型。</p>\n\n<h2 id=\"结论和未来的工作\">结论和未来的工作</h2>\n\n<p>PaLM 展示了 Pathways 系统在两个 TPU v4 Pod 上扩展到数千个加速器芯片的扩展能力,方法是使用经过充分研究、完善的密集解码器 Transformer 模型有效地训练一个 5400 亿个参数模型。 突破模型规模的极限,使 PaLM 在各种自然语言处理、推理和代码任务中实现突破性的小样本性能。</p>\n\n<p>PaLM 通过以下方式为功能更强大的模型铺平了道路将扩展能力与新颖的架构选择和培训方案相结合,使我们更接近 Pathways 的愿景:</p>\n\n<blockquote>\n <p>“使单个 AI 系统能够概括数千或数百万个任务,理解不同类型的数据,并以惊人的效率完成这些任务。”</p>\n</blockquote>\n\n<h2 id=\"致谢\">致谢</h2>\n\n<p>PaLM 是 Google Research 和整个 Alphabet 的许多团队共同努力的结果。 我们要感谢整个 PaLM 团队的贡献:Jacob Devlin、Maarten Bosma、Gaurav Mishra、Adam Roberts、Paul Barham、Hyung Won Chung、Charles Sutton、Sebastian Gehrmann、Parker Schuh、Kensen Shi、Sasha Tsvyashchenko、Joshua Maynez , Abhishek Rao, Parker Barnes, Yi Tay, Noam Shazeer, Vinodkumar Prabhakaran, Emily Reif, Nan Du, Ben Hutchinson, Reiner Pope, James Bradbury, Jacob Austin, Michael Isard, Guy Gur-Ari, Pengcheng Yin, Toju Duke, Anselm Levskaya , Sanjay Ghemawat, Sunipa Dev, Henryk Michalewski, Xavier Garcia, Vedant Misra, Kevin Robinson, Liam Fedus, Denny Zhou, Daphne Ippolito, David Luan, Hyeontaek Lim, Barret Zoph, Alexander Spiridonov, Ryan Sepassi, David Dohan, Shivani Agrawal, Mark Omernick、Andrew Dai、Thanumalayan Sankaranarayana Pillai、Marie Pellat、Aitor Lewkowycz、Erica Moreira、Rewon Child、Oleksandr Polozov、Katherine Lee、Zongwei Zhou、Xuezhi Wang、Brennan Saeta、Mark Diaz、Orhan Firat、Michele Catasta 和 Jason Wei。 PaLM 建立在谷歌许多团队的工作之上,我们特别要感谢 T5X 团队、Pathways 基础设施团队、JAX 团队、Flaxformer 团队、XLA 团队、Plaque 团队、Borg 团队,以及 数据中心网络基础架构团队。 我们要感谢这篇博文的共同作者 Alexander Spiridonov 和 Maysam Moussalem,以及 Josh Newlan 和 Tom Small 在这篇博文中提供的图像和动画。 最后,我们要感谢我们的项目顾问:Noah Fiedel、Slav Petrov、Jeff Dean、Douglas Eck 和 Kathy Meier-Hellstern。</p>\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n","<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>如何从语言模型中抽样:标准采样技术和新核采样的探索</title>\n \t<meta name=\"description\" content=\"麦克船长对于技术、产品、商业等领域的分享|AI,A.I.,NLP,神经网络,人工智能,自然语言处理,BERT,GPT,ChatGPT,OpenAI,阿里巴巴,P9,运营,淘宝,天猫,总监,高管\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n \t<!-- KaTeX -->\n \t<link rel=\"stylesheet\" href=\"/assets/plugins/katex.0.11.1/katex.min.css\">\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>如何从语言模型中抽样:标准采样技术和新核采样的探索</h2>\t\t\n\t<time datetime=\"2019-05-27T15:24:58+00:00\" class=\"by-line\">27 May 2019, 杭州 | Ben Mann | [译] AI & 麦克船长 | 总计 3108 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n\n<ul>\n <li>原文链接:https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277</li>\n</ul>\n\n<p><img src=\"\" alt=\"\" /></p>\n\n<p>↑ 人类通常会选择令语言模型感到惊讶的词(Holtzman 等人,2019 年)</p>\n\n<p>像 GPT-2 这样的因果语言模型被训练来预测在给定上下文的情况下下一个单词的概率。例如,给定「我吃了美味的热 ()」,模型可能以 80% 的概率预测「狗」,以 5% 的概率预测「煎饼」等。这种结构的妙处在于它们可用于生成<strong>任意序列长度</strong>。我可以给模型「我吃了 ()」,从结果分布中抽取一个标记以获得「我吃了一个 ()」,然后再次将其放入模型以获得另一个分布和结果标记,可以一直这样重复下去。事实证明,这一代语言模型往往是,要么陷入重复的循环,要么跑题。为什么会发生这种情况,我们如何更好地采样以生成更像人类的文本?</p>\n\n<p>这篇文章是 Holtzman 等人在 2019 年对<a href=\"https://arxiv.org/abs/1904.09751\">《The Curious Case of Neural Text De generation》</a>的总结和探索。我发现它是我最近读过的最透彻和可读性最强的论文之一,所以如果这篇文章引起共鸣,一定记得去读读它!</p>\n\n<p>如果我们总是对最有可能的词进行采样,标准语言模型训练目标会让我们陷入「我不知道。我不知道。我不知道。」 这是不自然的,但现代语言模型中模型的大部分注意力只集中在最近的几个标记上。相反,流行的生成采样方法是基于从分布中采样的。但是抽样也会遇到一个问题:如果我们有 50K 个可能的选择,即使底部的 25K 个标记每个都极不可能,它们加起来可能具有例如 30% 的概率质量。这意味着对于每个样本,我们有三分之一的机会完全偏离我们的「思路」。由于前面提到的短上下文,这将导致不可恢复的错误级联,因为每个下一个单词都严重依赖于最近的错误单词。</p>\n\n<p>为了对抗尾部采样,最流行的方法是温度(temperature)采样和前 k 采样。</p>\n\n<p>温度采样的灵感来自统计热力学,其中高温意味着更有可能遇到低能量状态。在概率模型中,logits 扮演能量的角色,我们可以通过将 logits 除以温度来实现温度采样,然后将它们输入 softmax 并获得我们的采样概率。例如:</p>\n\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"o\">>>></span> <span class=\"kn\">import</span> <span class=\"nn\">torch</span>\n<span class=\"o\">>>></span> <span class=\"kn\">import</span> <span class=\"nn\">torch.nn.functional</span> <span class=\"k\">as</span> <span class=\"n\">F</span>\n<span class=\"o\">>>></span> <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"n\">torch</span><span class=\"p\">.</span><span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mi\">1</span><span class=\"p\">,</span><span class=\"mi\">2</span><span class=\"p\">,</span><span class=\"mi\">3</span><span class=\"p\">,</span><span class=\"mf\">4.</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">/</span><span class=\"mf\">1.5</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.0708</span><span class=\"p\">,</span> <span class=\"mf\">0.1378</span><span class=\"p\">,</span> <span class=\"mf\">0.2685</span><span class=\"p\">,</span> <span class=\"mf\">0.5229</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.0321</span><span class=\"p\">,</span> <span class=\"mf\">0.0871</span><span class=\"p\">,</span> <span class=\"mf\">0.2369</span><span class=\"p\">,</span> <span class=\"mf\">0.6439</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">/</span><span class=\"p\">.</span><span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.0021</span><span class=\"p\">,</span> <span class=\"mf\">0.0158</span><span class=\"p\">,</span> <span class=\"mf\">0.1171</span><span class=\"p\">,</span> <span class=\"mf\">0.8650</span><span class=\"p\">])</span>\n<span class=\"o\">>>></span> <span class=\"n\">F</span><span class=\"p\">.</span><span class=\"n\">softmax</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"o\">/</span><span class=\"mf\">1e-6</span><span class=\"p\">,</span> <span class=\"n\">dim</span><span class=\"o\">=</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n<span class=\"n\">tensor</span><span class=\"p\">([</span><span class=\"mf\">0.</span><span class=\"p\">,</span> <span class=\"mf\">0.</span><span class=\"p\">,</span> <span class=\"mf\">0.</span><span class=\"p\">,</span> <span class=\"mf\">1.</span><span class=\"p\">])</span>\n</code></pre></div></div>\n\n<p>或者直观点:</p>\n\n<p><img src=\"\" alt=\"\" /></p>\n\n<p>较低的温度使模型对其最佳选择更置信,而大于 1 的温度会降低置信。0 温度相当于 argmax/max likelihood,无限大温度对应均匀采样。</p>\n\n<p><strong>Top K Sampling(前 k 采样)</strong>意味着按概率排序并将第 k 个标记以下的任何事物的概率归零。它似乎通过去尾并使其不太可能偏离主题来提升效果。但在某些情况下,确实有很多我们可以合理地从中采样的词(下面的广泛分布),而在某些情况下则没有(下面的狭窄分布)。</p>\n\n<p><img src=\"\" alt=\"\" /></p>\n\n<p>↑ 霍尔兹曼等人 2019</p>\n\n<p>为了解决这个问题,作者提出了「top p sampling」,又名「nucleus sampling」,其中我们计算累积分布并在 CDF 超过 P 时立即切断。在上面的广泛分布示例中,可能需要前 100 个令牌超过 top_p = .9。在窄分布中,我们的样本分布中可能已经超过了 top_p = .9,只有“热”和“暖”。通过这种方式,我们仍然可以避免对严重错误的标记进行采样,但可以在得分最高的标记置信度较低时保持多样性。</p>\n\n<p>为什么最大似然抽样不起作用?在训练过程中,永远不可能看到复合错误。该模型经过训练,可以根据人工生成的上下文预测下一个标记。如果它通过生成错误的分布而导致一个标记错误,则下一个标记将使用独立于上一个预测的“正确”人类生成的上下文。在生成期间,它被迫完成自己自动生成的上下文,这是它在训练期间没有考虑的设置。</p>\n\n<p>定性结果\n以下是使用 top_k=40 和上下文“I ate a delicious”的示例</p>\n\n<p>以下是使用 top_p=0.9 和相同的“我吃了一顿美味”上下文的示例:</p>\n\n<p>在这里自己试试吧!您可以在Runtime > Change runtime type中启用 GPU并获得大批量,无需额外的运行时间。</p>\n\n<p>超越论文:自动选择 p 和 k\n我发现很难确定这些样本中的哪一个更像人类。为此我设计了一个实验来确定top_k和top_p凭经验。</p>\n\n<p>我们的目标是使用 top_k 和 top_p 来最大化选择我们提供的实际下一个单词的概率。在搜索最佳 k 和 p 值时,实际上很容易通过分析确定给定样本。对于 k,我们找到出现“黄金”标记的排序索引。对于 p,我们找到黄金代币的 CDF。例如,如果上下文是“I ate a delicious hot”,而实际单词是“dog”,但模型的预测分布最有可能是“pancake”,我们将搜索概率,直到在以下位置找到“dog”索引 3。在索引 1 处,CDF 可能为 62%。在索引 3 处,CDF 可能约为 86%,因此我们将其记录为最佳 p。</p>\n\n<p>在许多示例中,我们可以计算最佳 p 和 k 值的直方图,并计算它们的汇总统计量。我在维基百科的随机部分进行了测试,上下文长度为 15。这比模型训练的长度 (1024) 短得多,但对于https://duet.li或聊天机器人等设置很常见。</p>\n\n<p>===== ks =====\n最高 29094.00\n均值 233.69\n中位数 3.00\n只有 13376.00\n===== ps =====\n最大 1.00\n均值 0.59\n中位数 0.60\n只有 13376.00\n随意在我的colab notebook中自己尝试。</p>\n\n<p>如果模型在其训练集上进行评估,则选择 top_k = 1 是最佳选择。但是由于模型稍微超出了域,因此最有可能的标记有时会出现在列表的更下方。此外,我们还有 50K 的 token 词汇表。在许多数据集中,我们永远不会看到所有标记,但模型对此并不确定。通过使用 top_p 或 top_k 将大部分概率质量归零,我们合并了我们的先验,从不选择这些从未见过的甚至在训练中的标记。</p>\n\n<p>也就是说,这种对 k 和 p 的搜索仍然在模型的世界观的背景下,因此它只是一个创可贴。我们真正想要的是修复训练。</p>\n\n<p>固定训练\n我也开始考虑改变训练目标以更好地匹配生成任务。例如,当模型生成看起来不像人类的整个序列时,我们是否可以训练某种鉴别器来惩罚模型?如何将 GAN 架构应用于非连续域并不简单。我遇到了Adversarial Text Generation without Reinforcement Learning和RL-based idea,但似乎这些还没有成为主流。我认为将这些想法应用于过去几个月席卷最先进技术的大型变形金刚会很有趣。</p>\n\n\n\t</div>\n</article>\n\n\n\n\t </main>\n\t\t\n\t\t <!-- Pagination links -->\n \n\n\t </div>\n\t \n\t <!-- Footer -->\n\t <footer><span>@2022 - MikeCaptain.com</span></footer>\n\n\n\t <!-- Script -->\n <script src=\"/js/main.js\"></script>\t\n\n\n\t</div>\n</body>\n</html>\n"]},"collections":[{"relative_directory":"_posts","docs":["<!DOCTYPE html>\n<html>\n\n<head>\n\t<!-- Meta -->\n\t<meta charset=\"UTF-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1\">\n\t<meta name=\"generator\" content=\"Jekyll\">\n\n\t<title>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 1:入门介绍、部署与 Hello World</title>\n \t<meta name=\"description\" content=\"RTMFP 是 Adobe 开发的基于 UDP 协议的实时传输媒体流协议,支持 P2P 传输,具有较高的实时性和安全性。它的主要应用场景是视频通信、语音通信和网络游戏。OpenRTMFP 是一个开源的 RTMFP 实现,可以用于构建基于 RTMFP 的应用程序。Cumulus 是一个基于 OpenRTMFP 的服务器,提供 RTMFP 服务。它具有轻量级、跨平台和可扩展的特点,并且还提供了负载均衡和可扩展性解决方案。YY 语音的 Web 端音视频流媒体能力,正是基于 RTMFP 协议做的迭代优化实现的。本文是船长关于这个系列文章的第一篇。\">\n\n\t<!-- CSS & fonts -->\n\t<link rel=\"stylesheet\" href=\"/css/main.css\">\n\n\t<!-- RSS -->\n\t<link href=\"/atom.xml\" type=\"application/atom+xml\" rel=\"alternate\" title=\"ATOM Feed\" />\n\n \t<!-- Favicon -->\n \t <link rel=\"shortcut icon\" type=\"image/png\" href=\"/img/favicon.png\">\n\n \t <!-- Syntax highlighter -->\n \t<link rel=\"stylesheet\" href=\"/css/syntax.css\" />\n\n \t<!--KaTeX-->\n \t<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css\" integrity=\"sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X\" crossorigin=\"anonymous\">\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js\" integrity=\"sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4\" crossorigin=\"anonymous\"></script>\n \t<script defer src=\"https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js\" integrity=\"sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa\" crossorigin=\"anonymous\"></script>\n \t<script>\n \t\tdocument.addEventListener(\"DOMContentLoaded\", function() {\n \t\t\trenderMathInElement(document.body, {\n \t\t\t\t// ...options...\n \t\t\t});\n \t\t});\n \t</script>\n\n \t\n\n</head>\n\n<body>\n\t<div id=\"wrap\">\n\t \t\n\t \t<!-- Navigation -->\n\t \t<nav id=\"nav\">\n\t<div id=\"nav-list\">\n\t\t<a href=\"/\">Home</a>\n\n\t\t<!-- Nav pages -->\n\t <!-- \n\t \n\t \n\t \n\t <a href=\"/about/\" title=\"关于我\">关于我</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n\t \n\t \n\t \n\t \n\t \n\t <a href=\"/categories/\" title=\"Categories\">Categories</a>\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t -->\n\n\t <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\t \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n\t <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n\t</div>\n \n <!-- Nav footer -->\n\t\n\t <footer>\n\t\n\t<span>version 1.0.0</span>\n\n</footer>\n\t\n\n</nav>\n\n \n <!-- Icon menu -->\n\t <a id=\"nav-menu\">\n\t \t<div id=\"menu\"></div>\n\t </a>\n\n <!-- Header -->\n \n <header id=\"header\" class=\"parent justify-spaceBetween\">\n <div class=\"inner w100 relative\">\n <span class=\"f-left\"> \n <a href=\"/\">\n <h1>\n <span>Mike</span>Captain\n </h1>\n </a>\n </span>\n <span id=\"nav-links\" class=\"absolute right bottom\">\n\n <!-- Tech category pages -->\n\n\n\n\n\n\n <a href=\"/category/ai\" title=\"人工智能\">人工智能</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/rt_tech\" title=\"实时技术\">实时技术</a>\n\n\n\n\n\n <a href=\"/category/web\" title=\"前端技术\">前端技术</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- Non-tech category pages -->\n\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/design\" title=\"设计\">设计</a>\n\n\n\n\n\n\n\n\n\n\n\n <a href=\"/category/thinking\" title=\"思考与生活\">思考与生活</a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n 丨 \n\n <!-- Nav pages -->\n \n \n \n \n <a href=\"/about/\" title=\"关于我\">关于我</a>\n \n \n \n \n \n \n \n <a href=\"/booklist/\" title=\"读书行路\">读书行路</a>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <!-- Nav links -->\n <!-- <a href=\"https://github.com/thereviewindex/monochrome/archive/master.zip\">Download</a>\n<a href=\"https://github.com/thereviewindex/monochrome\">Project on Github</a> -->\n\n </span>\n </div>\n</header>\n\n\n\n\n \n\n <!-- Main content -->\n\t <div id=\"container\">\n\t\t \n\t\t<main>\n\n\t\t\t<article id=\"post-page\">\n\t<h2>麦克船长的 OpenRTMFP/Cumulus 原理、源码及实践 1:入门介绍、部署与 Hello World</h2>\t\t\n\t<time datetime=\"2012-04-09T18:57:19+00:00\" class=\"by-line\">09 Apr 2012, 广州 | 麦克船长 | 总计 8401 字</time>\n\t<div class=\"content\">\n\t\t<p><strong>本文目录</strong></p>\n<ul id=\"markdown-toc\">\n <li><a href=\"#一rtmfp是什么\" id=\"markdown-toc-一rtmfp是什么\">一、RTMFP 是什么?</a> <ul>\n <li><a href=\"#文件分享-p2p-和实时流媒体-p2p-的区别是什么\" id=\"markdown-toc-文件分享-p2p-和实时流媒体-p2p-的区别是什么\">文件分享 P2P 和实时流媒体 P2P 的区别是什么?</a></li>\n <li><a href=\"#rtmfp-和-rtmp-之间的区别是什么\" id=\"markdown-toc-rtmfp-和-rtmp-之间的区别是什么\">RTMFP 和 RTMP 之间的区别是什么?</a></li>\n <li><a href=\"#flash-player-支持-rtmfp-吗\" id=\"markdown-toc-flash-player-支持-rtmfp-吗\">Flash Player 支持 RTMFP 吗?</a></li>\n <li><a href=\"#cumulus-使用-adobe-的-cirrus-key-吗\" id=\"markdown-toc-cumulus-使用-adobe-的-cirrus-key-吗\">Cumulus 使用 Adobe 的 Cirrus Key 吗?</a></li>\n <li><a href=\"#这个开源项目合法吗\" id=\"markdown-toc-这个开源项目合法吗\">这个开源项目合法吗?</a></li>\n </ul>\n </li>\n <li><a href=\"#二openrtmfp和cumulus\" id=\"markdown-toc-二openrtmfp和cumulus\">二、OpenRTMFP 和 Cumulus</a></li>\n <li><a href=\"#三入门介绍与部署cumulusserver\" id=\"markdown-toc-三入门介绍与部署cumulusserver\">三、入门介绍与部署 CumulusServer</a> <ul>\n <li><